Напишіть перекладача Shift


10

EDIT: Як деякі з вас підозрювали, в офіційному перекладачі виникла помилка: порядок складання композиції .був змінений. У мене було дві версії перекладача, і тут я використав неправильну. Приклади також були написані для цієї неправильної версії. Я зафіксував інтерпретатора у сховищі та наведені нижче приклади. Опис >також було дещо неоднозначним, тому я це виправив. Крім того, вибачення за це зайняло так довго, я потрапив у деякі речі з реального життя.

EDIT2: У мого перекладача була помилка, реалізація .якої відображалася в прикладах (вони покладалися на невизначену поведінку). Питання виправлено.

Вступ

Shift - це езотерична функціональна мова програмування, яку я створив пару років тому, але опублікував сьогодні. Він заснований на стеці, але також має автоматичну каррінг, як Haskell.

Специфікація

У Shift є два типи даних:

  • Функції, які мають довільну позитивну сутність (кількість входів) і які повертають список результатів. Наприклад, функція, що дублює єдиний вхід, має arity 1, а функція, яка переміняє два входи, має arity 2.
  • Пробіли, які всі однакові і не мають іншого призначення, крім того, щоб не бути функціями.

Програма Shift складається з нуля або більше команд , кожна з яких є одним символом ASCII. Всього 8 команд:

  • !( застосувати ) з'являється функція fта значення xзі стека та застосовується fдо x. Якщо fмає arity 1, список f(x)додається на передню частину стека. Якщо у нього є арітія n > 1, до стеку висувається нова (n-1)функція -ary g. Він займає введення та повернення .x1,x2,...,xn-1f(x,x1,x2,...,xn-1)
  • ?( blank ) проштовхує порожню до стека.
  • +( клон ) висуває в стек одинарну функцію, що дублює його вхід: будь-яке значення xвідображається на [x,x].
  • >( shift ) висуває в стек одинарну функцію, яка приймає функцію n-ary f, і повертає функцію (n+1)-ary, gяка ігнорує її перший аргумент x, викликає fрешту та прив'язує xперед результатом. Наприклад, shift(clone)це двійкова функція, яка приймає введення a,bта повернення [a,b,b].
  • /( fork ) висуває в стек потрійну функцію, яка займає три входи a,b,c, і повертається, [b]якщо aє порожнім, [c]інакше.
  • $( Виклик ) штовхає в стек двійкову функцію , яка з'являється функцією fі значення x, і застосовує fдо xточності так , як !робить.
  • .( Ланцюг ) штовхає в стек двійкову функцію , яка вискакує дві функції fі g, і повертає їх склад: функція , hяка має ту ж арность , як f, і який бере свої входи , як правило, відноситься fдо них, і потім повністю застосовується gдо результату (виклики вона стільки разів, скільки диктує її аритність), при цьому невикористані елементи з виходу fзалишаються в результаті h. Наприклад, припустимо, що fце двійкова функція, яка клонує свій другий аргумент і gє викликом . Якщо стек містить [f,g,a,b,c]і ми .!!, то він містить [chain(f,g),a,b,c]; якщо ми будемо робити !!далі, то fспочатку застосовуємо a,b, виробляючи[a,b,b], тоді gзастосовується до перших двох елементів цього, оскільки його аритість 2, створюючи [a(b),b], і стек нарешті буде [a(b),b,c].
  • @( скажімо ) висуває унарну функцію, яка просто повертає свій вхід, і друкує, 0якщо це було порожнім, і 1якщо це було функцією.

Зауважте, що всі команди, за винятком !просто натискання значення на стек, немає можливості виконати введення, і єдиний спосіб вивести що-небудь - це використовувати @. Програма інтерпретується шляхом оцінки команд по черзі, друку 0s або 1s кожного разу, коли викликається "сказати", та виходу з нього. Будь-яка поведінка, яка не описана тут (застосування бланка, нанесення стека довжиною 0 або 1, виклик "ланцюга" на бланку тощо), не визначено: перекладач може вийти з ладу, мовчки вийти з ладу, попросити ввести інформацію чи інше.

Завдання

Ваше завдання - написати перекладача для Shift. Потрібно взяти з STDIN, командного рядка або аргументу функції програму Shift, яку слід інтерпретувати, та роздрукувати в STDOUT або повернути отриманий (можливо нескінченний) вихід 0s і 1s. Якщо ви пишете функцію, ви повинні мати можливість отримати доступ до виходів нескінченної довжини якимось чином (генератор на Python, лінивий список у Haskell тощо). Крім того, ви можете взяти інший вхід, число nта повернути принаймні nсимволи виводу, якщо він довший за n.

Виграє найменший байт, а стандартні лазівки заборонені.

Випробування

Ця програма Shift друкує 01:

?@!@@!

Починаючи зліва: натисніть на бляху, натисніть сказати , а потім застосуйте приказку до порожнього. Це виводить 0. Потім натисніть " Скажіть двічі" і застосуйте друге слово до першого. Це виводить 1.

Ця програма циклічно вічно, не даючи результатів:

$+.!!+!!

Натисніть на виклик і клонуйте , а потім застосуйте до них ланцюг (нам потрібно два !s, оскільки ланцюг є бінарною функцією). Тепер стек містить функцію, яка бере один аргумент, дублює його і викликає першу копію на другу. З +!!, ми дублюємо цю функцію і викликаємо її на себе.

Ця програма друкує 0010:

?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!

Натисніть на бланк і скажіть . Потім складіть двійкову функцію, яка копіює її другий аргумент b, потім копіює перший aі складає його з собою, потім застосовує композицію до копії b, повертаючись [a(a(b)),b]. Застосуйте його до слова " слово" та "порожнє", а потім застосуйте " скажіть" до двох елементів, що залишилися в стеку.

Ця програма друкує 0. Для кожного, !!!що ви додаєте до нього, він друкує додатковий 0.

?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!

Натисніть на бланк і скажіть . Потім складіть потрійну функцію, яка приймає f,g,xв якості вхідних даних і повертається [f,f,g,g(x)]. Клоніруйте цю функцію та застосуйте її до себе, скажімо , та до порожнього. Ця програма не змінює стек, тому ми можемо застосувати цю функцію ще раз стільки разів, скільки нам захочеться.

Ця програма друкує нескінченну послідовність 001011011101111..., де кількість 1s завжди збільшується на одиницю:

@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!

У сховищі міститься примітка з приміткою.


Я тут трохи розгублений. Коли ви пишете "приймає", як у команді shift, ви маєте на увазі pops або ви маєте на увазі застосовану командою Apply?
tecywiz121

1
Крім того, я дійсно не впевнений у ваших характеристиках, як ланцюжок повинен працювати. Чи можете ви пояснити це прикладом, будь ласка?
tecywiz121

@ tecywiz121 Ось як я це розумію: скажіть, у вас є дві функції у верхній частині стека, f(x1, x2, ..., xn)і g(y1, y2, ..., ym). Виклик .вискакує обох і натискає функцію h(z1, z2, ..., zn). Тепер ви можете з'їсти всі ці аргументи, поступово погладжуючи їх !. Після nтаких додатків функція, що залишилася, мала лише один аргумент, і в цей момент вона обчислює f(z1, z2, ..., zn)(тобто fзастосовується до всіх аргументів, в яких ви прокляли), який виштовхує деякі нові значення, а потім негайно споживає mзначення зі стека і викликає gїх.
Мартін Ендер

@ MartinBüttner Якщо Згарб думає, що це відповідає правилам, ви можете використовувати другий вхідний параметр, що визначає максимальний розмір виводу. Це також було б вирішенням питання щодо ледачих оцінок.
randomra

@ tecywiz121 .працює точно так, як описав Мартін, за винятком того, що якщо fповертає список, менший за mзначення, результат не визначений (композиція має суворість n, тому він не може їсти більше аргументів зі стека). По суті, вихідний fфайл використовується як тимчасовий стек, на який gвисуваються і застосовуються mчаси за допомогою !, і результат цього додається до основного стеку.
Згарб

Відповіді:


12

Python 2, 752 667 534 506 445 436 427 404 398 393 байт

Це аж ніяк не коротко ... але я зробив все можливе. Будь-які пропозиції з гольфу були б дуже вдячні ...

EDIT6: Це тепер сценарій замість функції. Збережіть його у файлі (shift.py, форекс), після чого запустіть його $ python shift.py '<my_input>'. Переконайтесь, що введіть вкладку в одинарні лапки, інакше башш зійде з розуму від символів введення.

EDIT7: Ааааааа ... це вже не читабельно. Але я позбувся ще 23-х байт, тож це гарно, гадаю? Я також опублікую невмілену версію.

EDIT8: Ще один гольф, завдяки @Zgarb.

k,d=[],[]
u=k.append
def z(f,a=1):f.a=a;return f
exec "i=!x:x(*map(k.pop,[-1]*x.a)));e=dict(zip('?+>/$.@',[0,!x:u(x)<u(x)),!x:u(!a,*_:x(*_)<u(a),x.a+1))),!x,y,z:u((z,y)[x<1]),3),!x,y:u(!*_:x(y,*_),x.a-1))if x.a>1 else x(y),2),!x,y:u(!*_:x(*_)<i(y),x.a)),2),!x:d.append(`+(x>0)`)<u(x))]))".replace('!',"z(lambda ")
for _ in raw_input():
 try:[i,u][_ in e](e.get(_,e['$']))
 except:break
print d

EDIT: дякую @DLosc за допомогу в гольфі! Вдалося зменшити його на 85 байт.

EDIT2: вирізати тонну непотрібних обгортків і скинути її ще на 133 байти!

EDIT3: ... і ще 28 завдяки @ Sp3000 та @orlp у чаті!

EDIT4: за допомогою @orlp & @ Sp3000 видалено всі декоратори, і це на 61 байт коротше.

EDIT5: допоможіть meeeeee, я не можу припинити це гольф .... Ще 9 байтів пропало. Позбавлення остаточного оператора друку дозволить заощадити ще 7, але тоді, якщо запустити m () у циклі, весь вихід знаходиться в одному рядку ... це все в порядку?

Ось незворушена версія:

stack = []
push = stack.append

def arity(func,a=1): #give each of our functions an arity
    func.arity = a
    return func

def do(func): ##pop the args off the stack, then call the function
    args = map(stack.pop,[-1]*func.arity)
    func(*args)

def call(func,arg): #apply is just do(call)
    if func.arity == 1:
        func(arg)
    else:
        def curried(*a): #a quick little currier
            func(arg, *a)
        curried = arity(curried, func.arity - 1)
        push(curried)

def clone(arg):
    push(arg)
    push(arg)

def shift(func):
    def shifted(a, *arg):
        func(*arg)
        push(a)
    shifted = arity(shifted, func.arity + 1)
    push(shifted)

def fork(a, b, c):
    if a == 0:
        push(b)
    else:
        push(c)

def chain(func, gunc):
    def composition(*args):
        func(*args)
        do(gunc)
    composition = arity(composition, func.arity)
    push(composition)

def say(arg):
    print '10'[arg == 0],
    push(arg)

commands = {'?': 0,
            '+': arity(clone),
            '>': arity(shift),
            '/': arity(fork, 3),
            '$': arity(call, 2),
            '.': arity(chain, 2),
            '@': arity(say)}

def interpret(input_string):
    for command in input_string:
        try:
            if command == '!':
                do(call)
            else:
                push(commands[command])
        except RuntimeError: #this handles max recursion depth errors
            break            # for infinite programs
    print

if __name__ == "__main__":
    interpret(raw_input())

Основна ідея полягає в тому, що список python дуже добре працює як стек, і зберігаючи u=k.appendсимволи, я@u не тільки зберігаю символи, але і можу використовувати як декоратор для натискання функцій (вже не!).

Оскільки пару функцій, які діють на функції n-arity, потрібно вміти приймати довільну кількість аргументів, я повинен був використовувати *args, що означало, що мій початковий план відстеження f.func_code.co_argcount повинен був замінитися масивом атрибут декоратора .

Що стосується обробки нескінченних програм, то інтерпретатор працює, поки не досягне максимальної рекурсивної глибини; у нижній частині обробника RuntimeError в цьому місці тихо виходить, і він виводить поточний вихідний рядок потім.

Тестові приклади:

>>> tests
['?@!@@!', '$+.!!+!!', '?@$..!!+.!!+>!.!!!!+?/!!!@!@>!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!!!!', '@?/!@>!.!!??/!!>!.!!+.!!.+>!.!!$$.!!$.!!$.!!+.!!$>!>!.!!$>!>!.!!+>!.!!$>!>!>!.!!+>!>!.!!///!!>!>!>!.!!+!!!!!']
>>> for t in tests: m(t)
0 1

0 0 1 0
0
0 0
0 0 0
0 0 0 0
0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

1
Моя перша реакція: @ _ @ Серйозно, проте, приємна робота - розміщення фактичних функцій на стеці - це дійсно акуратне рішення. Кілька порад: 1) Термінальних операторів зазвичай можна скоротити так чи інакше . 2) Ви можете замінити ['1','0'][...]просто '10'[...]. 3) Чому x is 0і ні x==0(або x<1)? 4) Не турбуйтеся уточнюючи RuntimeError, просто exceptзроблять. 5) Оскільки ви використовуєте Python 2, вкладки та пробіли вважаються різними рівнями відступу - сміливо, але вони повинні заощадити ~ 25 байт.
DLosc

1
Ви повинні мати можливість вирізати це x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))- логічні оператори все ще мають коротке замикання, але використовують менше символів, ніж тернар. Потім збережіть інші байти, використовуючи в x.a-1якості умови (0 / помилкового , якщо xце 1, відмінно від нуля / вірно в іншому випадку) і поміняти місцями «потім» і «ще» вираження: x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y). (Маю ще шахту для гольфу ще раз, коли ти мене пройшов ...; ^))
DLosc

1
Натрапивши на подібну проблему з моєю, я розумію, що зараз не вдається - якщо x.a==1це правда, але x(y)повертає щось хибне, воно намагається оцінити u(...)також. Але це виглядає так, ніби вам не потрібно врятувати кволі 3 байти, які б вам дали! Я визнаю, пане: ви перевершили мене.
DLosc

1
Лише одна підказка: у вказаному форматі виводу немає пробілів - ви можете вирішити різні стратегії , не знаючи, який з них найкоротший. Звичайно, ваша програма обробляє час, RuntimeErrorпоки моя просто просить користувача перенаправити stderr ... так що ми, мабуть, навіть на каламбурах. ; ^)
DLosc

1
Для чого *_в лямбдах?
mbomb007

4

Привид

Ще не гольфували, оскільки мені ще потрібно відпрацювати функцію розбору.

Ця реалізація використовує _і :замість >і /, і вимагає , щоб всі символи програми повинні бути розділені пробілами. Це відбувається тому , що >і /не є допустимими іменами в Postscript, і оператори не саморазгранічно, але це буде виправлено , коли я пишу аналізатор.

Перша частина коду повинна бути досить прозорою, оскільки вона просто повторює визначення функцій оператора. Магія буває у визначенні !.

/switch {
    /y exch def
    /x exch def
    {x} {y} ifelse
} bind def

/unwrap {
    dup type (arraytype) eq {aload pop} if
} bind def

/! { % IN: <x> <f> OUT: <g>|<f(x)>
    [ 3 1 roll unwrap] cvx %prefix argument into function
    dup /fun exch def %bind

    [ count 1 roll ] { %run the function sandboxed so it can't take any additional args
        2 dict begin
        /=only {} def % suppress output
            {
                fun
            } stopped /err exch def clear err
        end
    } .runandhide


    exch {
        $error /errorname get
        (stackunderflow) ne {
            handleerror
        } if

        $error /newerror false put

        unwrap
    } {
        unwrap exec
    } ifelse
} def

/? 0 def
/+ {{dup}} def
/_ {{/f exch def pop f}} def % using _ instead of >
/: {{? ne 3 1 roll switch}} def % using : instead of /
/$ {{!}} def
/. {{/g exch def exec g}} def 
/@ {{dup ? eq {0}{1} ifelse =only}} def

Те , як !працює просто: По- перше, це додає аргумент xв fвипереджаючи xдо змісту f, штовхаючи його назад в стек, і називаючи копію результату fun.

Потім він загортає всю стек вгору як масив. .runandhideє розширенням Ghostscript для запуску пісочного коду, що приховує вміст попереднього масиву від процедури, на яку він викликається. dictКоманда висуває новий словник в стеці Dict, звужуючи сферу імен , визначених в межах до endударів не відступило. Він також замінює =only(оператор виводу, який я використовую @) на манекен, пригнічуючи вихід під час пробного запуску. stoppedє еквівалентом PostScript tryтвердження, знайденого на інших мовах, і воно повертається істинним, якщо його процедура видала помилку, і помилковою, якщо вона закінчилася до завершення.

Після завершення пробного запуску funпрограма відновлює початковий стек із прихованого масиву, і якщо funзавершено без помилок, то він запускає його для реального, зберігаючи вихід.


2

Python3, 685 670 634 633 байт

Я майже впевнений, що це найдовше, що я коли-небудь займався гольфом. Раніше це було дещо читабельним, але, слідуючи порадам @ sirpercival, усунув цей недолік!

from re import*
E,*k="E"
P="e(k.pop(),k.pop())"
def H(a,b):global k;k+=list(a)+[N(b)];exec("k+=%s;"%P*Z(N(b)));return[]
def e(a,b):a=sub("(?<!\d)0",repr(N(b,1)).replace("\\",r"\\"),a,1);return Z(a)and[a]or list(eval(a))
D=list(zip("ilhydsSNZ",[3,2,2]+[1]*6,sub("A","N(a)",',b,c:[N([b,c][a>E])]|,b:e(A,N(b))|,b:["H(%s,%s)"%(A,repr(b))]|:print(0+(a>E),end="")or[A]|:[A]*2|:["S(0,%s)"%A]|,b:b+[A]|,b=-1:sub("\d+",lambda m:str(int(m.group())+b),a)|:len(split("\D0",a))-1').split("|")))
for n,r,f in D:exec(n+"=lambda a"+f)
F=dict(zip("/$.@+>?!",D))
for z in input():n,r,f=F[z];k+=z!="!"and[[n+"(%s)"%",".join("0"*r),E][z=="?"]]or eval(P)

kце стек, який містить функції, представлені у вигляді рядків типу "h(0,0)"(що є c h ain ). Коли функція передається в якості аргументу іншої функції, він отримує repr«d і все числа збільшується: "h('h(1,1)',0)". Після того, як всі 0s замінені у функції, вся справа передається eval, тим самим викликаючи відповідну функцію Python - більшість з яких - це лямбда-функції, згенеровані з великого рядка в рядку 6 execв рядку 7.

Отримати декілька рівнів вкладених функцій, збільшивши, процитувавшись та уникнувши належним чином, було найбільшим головним болем. Я міг би заощадити трохи більше на операціях регулярного виведення, якщо я можу припустити, що вкладення функцій не буде проходити глибше 9 рівнів, але, як зазначено в коментарях, це, мабуть, не є безпечним припущенням.

Ungolfed попередня версія коду:

from re import *
E="E"
stack=[]

clone=lambda a:[unnest(a)]*2
shift=lambda a:["shifted(0,%s)"%unnest(a)]
fork=lambda a,b,c:[unnest(c if a!=E else b)]
call=lambda a,b:apply(unnest(a),unnest(b))
chain=lambda a,b:["chained(%s,%s)"%(unnest(a),repr(b))]
def say(a):
 print(1 if a!=E else 0,end="")
 return [unnest(a)]

shifted=lambda a,b:b+[unnest(a)]
def chained(a,b):
 global stack
 stack+=list(a)+[unnest(b)]
 exec("stack+=apply(stack.pop(),stack.pop());"*zeros(unnest(b)))
 return []

nest=lambda a,direction:sub("\d+",lambda m:str(int(m.group())+direction),a)
unnest=lambda a:nest(a,-1)
zeros=lambda a:len(split("\D0",a))-1
def apply(a,b):
 a=sub("(?<!\d)0",repr(nest(b,1)).replace("\\",r"\\"),a,1)
 return [a] if zeros(a) else list(eval(a))

functions=dict(zip("+>/$.@",zip(["clone","shift","fork","call","chain","say"],[1,1,3,2,2,1])))

for cmd in input():
 if"!"==cmd:
  stack+=apply(stack.pop(),stack.pop())
 elif"?"==cmd:
  stack+=[E]
 else:
  name,arity=functions[cmd]
  stack+=[name+"(%s)"%",".join("0"*arity)]

Єдиним потенційним недоліком цієї реалізації є те, що вона використовує рекурсію, тому програми, які повинні бути нескінченними, досить швидко вражають максимальну глибину рекурсії. (Напевно, ви хочете перенаправити stderr, коли запускаєте нескінченну програму - інакше слід стека зможе фактичний вихід.) Крім цього, все, здається, працює.


Чи можете ви написати програму, яка генерує вищевказану програму, а потім виконати її? У вас є багато повторюваних кодів, які повинні бути стислими, як lambda aі k.pop().
mbomb007

@ mbomb007 ... Я думаю, мій мозок вибухне. (Але дивіться нещодавню редагування - я все-таки зробив k.pop()ситуацію трохи повторюваною.)
DLosc

чи можете ви виконати / перекласти трюк для всіх цих лямбдів? приклеїти їх усі в одну струну?
sirpercival

ще один коментар: я сумніваюся, що ви можете покластися на функцію гніздування <= 9 цією мовою
sirpercival

@sirpercival Так, я думав спробувати це. І ні, я гадаю, що ні. : ^ P
DLosc

1

Цейлон, 1167 1057 1031

Я не вважаю це таким коротким, як однотипні версії пітона ...

import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}

Ось відформатована (та коментована) версія того ж коду (з пробілами / новинками / коментарями вона стає 4867 байтами):

import ceylon.language.meta.model {
    N=Function
}
import ceylon.collection {
    H=HashMap
}
//↑ Import of stuff we need – with a shorter alias.
// (The comment is down here due to a bug in my comment and space
//  remover – it doesn't remove a comment if it is the first token
//  at all.)

// Our data items are either functions or blanks.
interface D of F | b {}

// There is no point in having many blanks – so here a singleton.
object b satisfies D {}

// The function class. Our functions take a number of data items,
// and return a number of data items.
// We know the arity a, and have also an actual function f, and a number
// or already collected arguments.
class F(shared Integer a, [D+](D+) f, [D*] c = [])
        satisfies D {
    // apply once (= collect one parameter). Returns either the result,
    // or a function with arity one less.
    shared [D+] o(D i) {
        [D+] s = [i].prepend(c);
        return a == 1 then f(*s) else [F(a - 1, f, s)];
    }
    // apply fully (= with all needed parameters).
    // The input size should equal the arity.
    shared [D+] y([D+] i) {
        // merge collected and input arguments.
        return f(*i.prepend(c));
    }
}
// creates a shift function from a ceylon function,
// deriving the arity using reflection.
F m<A>(N<[D+],A> f)
        given A satisfies [D+]
        => F(f.parameterTypes.size, (D+ i) => f.apply(*i));

//
// clone: a unary function that duplicates its input: any value x is mapped to [x,x].
//
[D, D] e(D x) => [x, x];

//
// shift: a unary function that takes in an n-ary function f, and returns an
// (n+1)-ary function g that ignores its first argument x, calls f on the
// remaining ones, and tacks x in front of the result. For example,
// shift(clone) is a binary function that takes inputs a,b and returns [a,b,b].
//
[F] t(F f) {
    [D+] g(D+ i) {
        assert (is [D+] r = i.rest);
        return [i[0], *f.y(r)];
    }
    return [F(f.a + 1, g)];
}

//
// fork: a ternary function that takes three inputs a,d,c, and returns [d] if a is a blank,
// and [c] otherwise.
//
[D] k(D a, D d, D c) => a == b then [d] else [c];

//
// call: a binary function that pops a function f and a value x,
//        and applies f to x exactly as ! does.
//
[D+] l(F a, D x) => a.o(x);

//
// chain:  a binary function that pops two functions f and g, and returns their composition:
//         a function h that has the same arity as f, and which takes its inputs normally, applies
//         f to them, and then fully applies g to the result (calls it as many times as its arity
//         dictates), with unused items from the output of f remaining in the result of h. For
//         example, suppose that f is a binary function that clones its second argument, and
//         g is call. If the stack contains [f,g,a,b,c] and we do .!!, then it contains
//         [chain(f,g),a,b,c]; if we do !! next, then f is first applied to a,b, producing
//         [a,b,b], then g is applied to the first two elements of that since its arity is 2,
//         producing [a(b),b], and the stack will finally be [a(b),b,c].
//
[F] n(F f, F g) {
    [D+] h(D+ i) {
        // call f, remember the results.
        [D+] r = f.y(i);
        // first some results from f are the arguments to g:
        assert (is [D+] d = r[0:g.a]);
        // remaining results from f are passed back directly, with the results from g.
        return g.y(d).append(r[g.a...]);
    }
    return [F(f.a, h)];
}

//
// say: a unary function that simply returns its input, and prints 0 if it was a blank,
//      and 1 if it was a function.
// 
[D] y(D x) {
    process.write(x == b then "0" else "1");
    return [x];
}

//
// Interpreter class, which manages the stack and interprets the commands.
// Just call the r method with the individual command characters.
//
class I() {
    // The stack. The only variable in the whole program.
    variable D[] s = [];

    // a hash map of items to be pushed by commands, most build using the m function.
    // The apply command is not here, this is handled separately by the interpreter. 
    value c = H {
        '?'->b,
        '+'->m(`e`),
        '>'->m(`t`),
        '/'->m(`k`),
        '$'->m(`l`),
        '.'->m(`n`),
        '@'->m(`y`)
    };

    // Interprets one command, indicated by a character.
    // Will throw an AssertionError for unknown commands.
    shared void r(Character i) {
        if (i == '!') {
            assert (
                is F f = s[0],
                is D x = s[1]);
            // apply f on x, push the result onto a shortened version of the stack.
            s = f.o(x).append(s[2...]);
        } else {
            assert (is D d = c[i]);
            // push d on top of the stack.
            s = [d].append(s);
        }
    }
}

shared void z() {
    process.readLine()?.collect(I().r);
}

Функції клонування e, зсуву t, розсилки k, виклику l, говоріння yта ланцюга nвикористовують останню букву імен для скороченої версії, оскільки це дало менше зіткнень. (Trivia: fork був спочатку визначений таким чином: [Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];- коли я перейменував blankна b, це зламалось, тому що тепер він порівнював параметри aта bзамість aцього пробіл. Знадобився деякий час для налагодження.)

zФункція розділяє , тому що мій IDE працює ці функції - інструмент командного рядка можна також запустити неподільні з них.


Версії циклу фактично кинуть StackOverflowError в якийсь момент, закінчуючи потім. JVM не має оптимізації стека рекурсії (або принаймні жодної, яка працювала б для моєї програми).
Paŭlo Ebermann
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.