Оптимізація компілятора SKI


22

Обчислення SKI варіант обчислення лямбда , який не використовує лямбда - вираження. Замість цього, тільки додатки і комбінаторів S , K і I використовуються. У цьому виклику ваше завдання полягає в тому, щоб перевести терміни SKI в терміни Лямбда в β нормальну форму .


Специфікація вводу

Вхід - термін SKI у наступному текстовому поданні. Ви можете отримати додатковий новий рядок. Введення складається з символів S, K, I, (, і )та задовольняє наступній граматикою (у вигляді ABNF) з stermбудучи початковим символом:

sterm = sterm combinator     ; application
sterm = combinator           ;
sterm = '(' sterm ')'        ; grouping
combinator = 'S' | 'K' | 'I' ; primitives

Вихідні характеристики

Вихід - це лямбда-термін без вільних змінних у наступному текстовому поданні. Ви можете вибрати додатковий новий рядок. Вихід повинен відповідати наступній граматиці у формі ABNF із ltermсимволом старту:

lterm   = lterm operand     ; application
lterm   = ALPHA '.' lterm   ; lambda
lterm   = operand
operand = '(' lterm ')'     ; grouping
operand = ALPHA             ; variable (a letter)

Обмеження

Можна припустити, що вхід має β нормальну форму. Можна припустити, що нормальна форма β використовує щонайменше 26 різних змінних. Ви можете припустити, що і вхід, і вихід відображаються в 79 символах.

Зразки входів

Ось пара зразків даних. Вихід - це один можливий вихід для даного входу.

input                        output
I                            a.a
SKK                          a.a
KSK                          a.b.c.ac(bc)
SII                          a.aa

Оцінка балів

Виграє найкоротше рішення в октетах. Поширені лазівки заборонені.


7
+1, тому що я припускаю, що це класна проблема; Я не зрозумів цього слова.
Олексій А.

2
Ах, я повинен гольфувати свої ski.aditsu.net :)
aditsu

Напевно, ви повинні сказати, що обидва stermі ltermвикористовувати ліву асоціативність, коли дужки відсутні.
Пітер Тейлор

@PeterTaylor Краще таким чином?
FUZxxl

Ні, я думаю, що це насправді неправильно: після цієї зміни граматики я би розбирав SKIяк S(KI).
Пітер Тейлор

Відповіді:


3

Haskell , 232 байти

data T s=T{a::T s->T s,(%)::s}
i d=T(i. \x v->d v++'(':x%v++")")d
l f=f`T`\v->v:'.':f(i(\_->[v]))%succ v
b"S"x=l$l.(a.a x<*>).a
b"K"x=l(\_->x)
b"I"x=x
p?'('=l id:p
(p:q:r)?')'=a q p:r
(p:q)?v=a p(l$b[v]):q
((%'a')=<<).foldl(?)[l id]

Спробуйте в Інтернеті!

Як це працює

Це інший фронтальний синтаксичний аналіз на мою відповідь на "Написати перекладача для нетипізованого обчислення лямбда" , який також має необроблену версію з документацією.

Коротко, Term = T (Char -> String)це тип термінів обчислення лямбда, які знають, як застосувати себе до інших термінів ( a :: Term -> Term -> Term) та як відобразити себе як String( (%) :: Term -> Char -> String), задавши початкову свіжу змінну як a Char. Ми можемо перетворити функцію за термінами в термін l :: (Term -> Term) -> Term, і оскільки застосування отриманого терміна просто викликає функцію ( a (l f) == f), умови відображення автоматично зменшуються до нормальної форми.


9

Рубін, 323 байти

Я взагалі не можу повірити, що це лайно працює:

h={};f=96;z=gets.chop
{?S=>'s0.t0.u0.s0u0(t0u0)',?K=>'k0.l0.k0',?I=>'i0.i0'}.each{|k,v|z.gsub!k,?(+v+?)}
loop{z=~/\((?<V>\w1*0)\.(?<A>(?<B>\w1*0|[^()]|\(\g<B>+\))+)\)(?<X>\g<B>)/
s=$`;t=$';abort z.gsub(/\w1*0/){|x|h[x]=h[x]||(f+=1).chr}if !t
z=$`+?(+$~[?A].gsub($~[?V],$~[?X].gsub(/\w1*0/){|a|s[a]?a:a.gsub(?0,'10')})+?)+t}

Використання заміни регулярних виразів для зменшення β-скорочення для сирих рядків є деякими матеріалами Тоні-Поні. Тим не менш, його результат виглядає правильним принаймні для легких тестів:

$ echo 'I' | ruby ski.rb
(a.a)
$ echo 'SKK' | ruby ski.rb
(a.(a))
$ echo 'KSK' | ruby ski.rb
((a.b.c.ac(bc)))
$ echo 'SII' | ruby ski.rb
(a.(a)((a)))

Ось це обробка K(K(K(KK)))з деяким налагодженням виводу, який займає близько 7 секунд на моєму ноутбуці, тому що регулярна рекурсія виразів повільна . Ви можете бачити його перетворення α в дії!

$ echo 'K(K(K(KK)))' | ruby ski.rb
"(l0.((k10.l10.k10)((k10.l10.k10)((k10.l10.k10)(k10.l10.k10)))))"
"(l0.((l10.((k110.l110.k110)((k110.l110.k110)(k110.l110.k110))))))"
"(l0.((l10.((l110.((k1110.l1110.k1110)(k1110.l1110.k1110)))))))"
"(l0.((l10.((l110.((l1110.(k11110.l11110.k11110))))))))"
(a.((b.((c.((d.(e.f.e))))))))

Я отримую: ski.rb: 4: у `gsub ': неправильний аргумент типу nil (очікуваний Regexp) (TypeError) із прикладом' я '
aditsu

Слід виправити зараз! Я вже виправив це локально, але забув редагувати свою публікацію.
Лінн

2
Гаразд, це ........ l ....................... о ........... ш, але, здається, спрацює .... зрештою :) Я думаю, що результат для S (K (SI)) K, однак, невірний.
aditsu

9

Пітон 2, 674

exec u"""import sys
$ V#):%=V.c;V.c+=1
 c=97;p!,v,t:[s,t.u({})][v==s];r!:s;u!,d:d.get(s,s);s!:chr(%)
 def m(s,x):%=min(%,x);-(%==x)+x
$ A#,*x):%,&=x
 C='()';p!,x,y:s.__$__(%.p/,&.p/);m!,x:&.m(%.m(x));u!,d:A(%.u(d),&.u(d));s!:%.s()+s.C[0]+&.s()+s.C[1:]
 def r(s):x=%.r();y=&.r();-x.b.p(x.a,y).r()if'L'in`x`else s.__$__/
$ L(A):C='.';u!,d:L(d.setdefault(%,V()),&.u(d))
x=V();y=V();z=V()
I=L(x,x)
K=L(y,L/)
S=L(x,L(z,L(y,A(A/,A(z,y)))))
def E():
 t=I
 while 1:
    q=sys.stdin.read(1)
    if q in')\\n':-t
    t=A(t,eval(max(q,'E()')).u({}))
t=E().r()
t.m(97)
print t.s()""".translate({33:u'=lambda s',35:u':\n def __init__(s',36:u'class',37:u's.a',38:u's.b',45:u'return ',47:u'(x,y)'})

Примітка: після цього while 1:3 рядки відступають символом вкладки.

Це в основному код, що стоїть за http://ski.aditsu.net/ , в перекладі на python, значно спрощений і значно полегшений.

Довідка: (це, мабуть, менш корисно зараз після стискання коду)

V = змінний термін
A = термін застосування
L = лямбда-термін
c = лічильник змінної
p = замінити змінну терміном
r = зменшити
m = остаточна змінна перенумерування
u = внутрішня змінна перенумерування (для дублюваних термінів)
s = перетворення рядка
(параметр s = self)
C = символи (роздільники) для перетворення рядків
I, K, S: комбінатори
E = розбір

Приклади:

python ski.py <<< "KSK"
a.b.c.a(c)(b(c))
python ski.py <<< "SII"        
a.a(a)
python ski.py <<< "SS(SS)(SS)"
a.b.a(b)(c.b(c)(a(b)(c)))(a(d.a(d)(e.d(e)(a(d)(e))))(b))
python ski.py <<< "S(K(SI))K" 
a.b.b(a)
python ski.py <<< "S(S(KS)K)I"                   
a.b.a(a(b))
python ski.py <<< "S(S(KS)K)(S(S(KS)K)I)"        
a.b.a(a(a(b)))
python ski.py <<< "K(K(K(KK)))"
a.b.c.d.e.f.e
python ski.py <<< "SII(SII)"
[...]
RuntimeError: maximum recursion depth exceeded

(цього ↑ очікується тому, що SII(SII) може бути зменшеним)

Дякую Маурісу та Sp3000 за те, що вони допомогли вбити купу байтів :)


1
Я впевнений , що ви можете перетворити def m(a,b,c):return fooв m=lambda a,b,c:fooнавіть всередині класів, які могли б заощадити багато байт.
Лінн

@Mauris дякую за підказку :)
aditsu

Я не можу прочитати a.b.c.a(c)(b(c))як дійсне лямбда-вираз: що таке (c)?
coredump

@coredump - це операнд із непотрібною групуванням ... і ти маєш рацію, він не зовсім відповідає граматичним правилам ОП. Цікаво, наскільки це важливо; Я запитаю.
aditsu

@coredump Це має бути нормально зараз із оновленою граматикою.
aditsu

3

Лист звичайний, 560 байт

"Нарешті, я знайшов собі застосування PROGV".

(macrolet((w(S Z G #1=&optional(J Z))`(if(symbolp,S),Z(destructuring-bind(a b #1#c),S(if(eq a'L),G,J)))))(labels((r(S #1#(N 97))(w S(symbol-value s)(let((v(make-symbol(coerce`(,(code-char N))'string))))(progv`(,b,v)`(,v,v)`(L,v,(r c(1+ n)))))(let((F(r a N))(U(r b N)))(w F`(,F,U)(progv`(,b)`(,U)(r c N))))))(p()(do((c()(read-char()()#\)))q u)((eql c #\))u)(setf q(case c(#\S'(L x(L y(L z((x z)(y z))))))(#\K'(L x(L u x)))(#\I'(L a a))(#\((p)))u(if u`(,u,q)q))))(o(S)(w S(symbol-name S)(#2=format()"~A.~A"b(o c))(#2#()"~A(~A)"(o a)(o b)))))(lambda()(o(r(p))))))

Безумовно

;; Bind S, K and I symbols to their lambda-calculus equivalent.
;;
;; L means lambda, and thus:
;;
;; -  (L x S) is variable binding, i.e. "x.S"
;; -  (F x)   is function application

(define-symbol-macro S '(L x (L y (L z ((x z) (y z))))))
(define-symbol-macro K '(L x (L u x)))
(define-symbol-macro I '(L x x))

;; helper macro: used twice in R and once in O

(defmacro w (S sf lf &optional(af sf))
  `(if (symbolp ,S) ,sf
       (destructuring-bind(a b &optional c) ,S
         (if (eq a 'L)
             ,lf
             ,af))))

;; R : beta-reduction

(defun r (S &optional (N 97))
  (w S
      (symbol-value s)
      (let ((v(make-symbol(make-string 1 :initial-element(code-char N)))))
        (progv`(,b,v)`(,v,v)
              `(L ,v ,(r c (1+ n)))))
      (let ((F (r a N))
            (U (r b N)))
        (w F`(,F,U)(progv`(,b)`(,U)(r c N))))))

;; P : parse from stream to lambda tree

(defun p (&optional (stream *standard-output*))
  (loop for c = (read-char stream nil #\))
        until (eql c #\))
        for q = (case c (#\S S) (#\K K) (#\I I) (#\( (p stream)))
        for u = q then `(,u ,q)
        finally (return u)))

;; O : output lambda forms as strings

(defun o (S)
  (w S
      (princ-to-string S)
      (format nil "~A.~A" b (o c))
      (format nil (w b "(~A~A)" "(~A(~A))") (o a) (o b))))

Бета-зменшення

Змінні динамічно прив'язуються під час скорочення PROGVдо нових загальних символів Lisp, використовуючи MAKE-SYMBOL. Це дозволяє добре уникнути іменування зіткнень (наприклад, небажане затінення зв'язаних змінних). Я міг би використати GENSYM, але ми хочемо мати зручні назви символів. Ось чому символи іменуються літерами від aдо z(як це дозволяє питання). Nпредставляє код символу наступної доступної літери в поточному масштабі і починається з 97, акаa .

Ось більш читаема версія R(без Wмакросу):

(defun beta-reduce (S &optional (N 97))
  (if (symbolp s)
      (symbol-value s)
      (if (eq (car s) 'L)
          ;; lambda
          (let ((v (make-symbol (make-string 1 :initial-element (code-char N)))))
            (progv (list (second s) v)(list v v)
              `(L ,v ,(beta-reduce (third s) (1+ n)))))
          (let ((fn (beta-reduce (first s) N))
                (arg (beta-reduce (second s) N)))
            (if (and(consp fn)(eq'L(car fn)))
                (progv (list (second fn)) (list arg)
                  (beta-reduce (third fn) N))
                `(,fn ,arg))))))

Проміжні результати

Розбір з рядка:

CL-USER> (p (make-string-input-stream "K(K(K(KK)))"))
((L X (L U X)) ((L X (L U X)) ((L X (L U X)) ((L X (L U X)) (L X (L U X))))))

Скоротити:

CL-USER> (r *)
(L #:|a| (L #:|a| (L #:|a| (L #:|a| (L #:|a| (L #:|b| #:|a|))))))

(Див. Сліди виконання)

Симпатичний друк:

CL-USER> (o *)
"a.a.a.a.a.b.a"

Тести

Я повторно використовую той самий набір тестів, що і відповідь Python:

        Input                    Output              Python output (for comparison)

   1.   KSK                      a.b.c.a(c)(b(c))    a.b.c.a(c)(b(c))              
   2.   SII                      a.a(a)              a.a(a)                        
   3.   S(K(SI))K                a.b.b(a)            a.b.b(a)                      
   4.   S(S(KS)K)I               a.b.a(a(b))         a.b.a(a(b))                   
   5.   S(S(KS)K)(S(S(KS)K)I)    a.b.a(a(a(b)))      a.b.a(a(a(b)))                
   6.   K(K(K(KK)))              a.a.a.a.a.b.a       a.b.c.d.e.f.e 
   7.   SII(SII)                 ERROR               ERROR

8-й тестовий приклад занадто великий для таблиці вище:

8.      SS(SS)(SS)
CL      a.b.a(b)(c.b(c)(a(b)(c)))(a(b.a(b)(c.b(c)(a(b)(c))))(b))      
Python  a.b.a(b)(c.b(c)(a(b)(c)))(a(d.a(d)(e.d(e)(a(d)(e))))(b))
  • EDIT Я оновив свою відповідь, щоб мати таку групову поведінку, як у відповіді aditsu , оскільки для запису це коштує менше байтів.
  • Частина, що залишилася різниця може розглядатися для випробувань 6 і 8. Результат a.a.a.a.a.b.aє правильним і не використовує стільки ж букви , як відповідь на Python, де прив'язки до a, b, cі dне посилаються.

Продуктивність

Перегляд вищепроведених 7 тестів та збирання результатів негайний (вихід SBCL):

Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  310,837 processor cycles
  129,792 bytes consed

Проведення ж тесту в сто разів призводить до ... "Місцеве сховище потоків вичерпане" на SBCL, через відоме обмеження щодо спеціальних змінних. За допомогою CCL виклик одного і того ж тестового набору 10000 разів займає 3,33 секунди.


Це акуратне рішення!
FUZxxl

@FUZxxl Дякую!
coredump
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.