Ubah ekspresi λ menjadi ekspresi-SK


20

The λ-kalkulus , atau lambda kalkulus, adalah sistem yang logis berdasarkan fungsi anonim. Sebagai contoh, ini ekspresi λ:

λf.(λx.xx)(λx.f(xx))

Namun, untuk keperluan tantangan ini, kami akan menyederhanakan notasi:

  • Ubah λke \(untuk mempermudah mengetik):\f.(\x.xx)(\x.f(xx))
  • The .dalam header lambda tidak perlu, sehingga kita bisa menjatuhkannya:\f(\xxx)(\xf(xx))
  • Gunakan notasi gaya- Unlambda dengan `untuk aplikasi daripada menulis dua fungsi bersama-sama (untuk penjelasan lengkap tentang bagaimana melakukan ini, lihat Mengkonversi antara Notasi Kalkulus Lambda ):\f`\x`xx\x`f`xx
  • Ini adalah substitusi yang paling rumit. Ganti setiap variabel dengan angka dalam tanda kurung berdasarkan seberapa dalam bersarang variabel relatif terhadap header lambda miliknya (yaitu menggunakan pengindeksan De Bruijn berbasis 0 ). Misalnya, dalam \xx(fungsi identitas), xdi dalam tubuh akan diganti dengan [0], karena itu milik header pertama (berbasis 0) yang ditemui ketika melintasi ekspresi dari variabel ke akhir; \x\y``\x`xxxyakan dikonversi menjadi \x\y``\x`[0][0][1][0]. Kita sekarang dapat menjatuhkan variabel di header, pergi \\``\`[0][0][1][0].

Logika kombinasional pada dasarnya adalah Turing Tarpit yang terbuat dari λ-kalkulus (Ya, sebenarnya, itu yang didahulukan , tapi itu tidak relevan di sini.)

"Logika kombinatori dapat dilihat sebagai varian dari kalkulus lambda, di mana ekspresi lambda (mewakili abstraksi fungsional) digantikan oleh seperangkat kombinator terbatas, fungsi primitif dari mana variabel terikat tidak ada." 1

Tipe paling umum dari logika kombinatorik adalah kalkulus kombinator SK , yang menggunakan primitif berikut:

K = λx.λy.x
S = λx.λy.λz.xz(yz)

Terkadang kombinator I = λx.xditambahkan, tetapi berlebihan, karena SKK(atau memang SKxuntuk apa pun x) setara dengan I.

Yang Anda butuhkan adalah K, S, dan aplikasi untuk dapat menyandikan ekspresi apa pun dalam kalkulus λ. Sebagai contoh, berikut ini adalah terjemahan dari fungsi λf.(λx.xx)(λx.f(xx))ke logika kombinasi:

λf.(λx.xx)(λx.f(xx)) = S(K(λx.xx))(λf.λx.f(xx))
λx.f(xx) = S(Kf)(S(SKK)(SKK))
λf.λx.f(xx) = λf.S(Kf)(S(SKK)(SKK))
λf.S(Sf)(S(SKK)(SKK)) = S(λf.S(Sf))(K(S(SKK)(SKK)))
λf.S(Sf) = S(KS)S
λf.λx.f(xx) = S(S(KS)S)(K(S(SKK)(SKK)))
λx.xx = S(SKK)(SKK)
λf.(λx.xx)(λx.f(xx)) = S(K(S(SKK)(SKK)))(S(S(KS)S)(K(S(SKK)(SKK))))

Karena kita menggunakan notasi awalan, ini ```S`K``S``SKK``SKK``S``S`KSS`K``SKK`.

1 Sumber: Wikipedia

Tantangan

Sekarang, Anda mungkin sudah menebak apa itu: Tulis program yang menggunakan ekspresi λ yang valid (dalam notasi yang dijelaskan di atas) sebagai input dan output (atau mengembalikan) fungsi yang sama, ditulis ulang dalam kalkulus SK-combinator. Perhatikan bahwa ada sejumlah cara tak terbatas untuk menulis ulang ini; Anda hanya perlu menampilkan salah satu cara tak terbatas.

Ini adalah , sehingga pengiriman terpendek yang valid (diukur dalam byte) menang.

Uji Kasus

Setiap test case menunjukkan satu kemungkinan output. Ekspresi di atas adalah ekspresi setara λ-kalkulus.

λx.x:
\[0]                        -> ``SKK
λx.xx:
\`[0][0]                    -> ```SKK``SKK
λx.λy.y:
\\[0]                       -> `SK
λx.λy.x:
\\[1]                       -> K
λx.λy.λz.xz(yz):
\\\``[2][0]`[1][0]          -> S
λw.w(λx.λy.λz.xz(yz))(λx.λy.x):
\``[0]\\[1]\\\``[2][0]`[1][0] -> ``S``SI`KS`KK


1
Saya pikir test case kedua Anda tidak benar. Yang terakhir berisi angka yang tidak ada dalam tanda kurung.
Christian Sievers


Bagaimana kau bisa λx.f(xx) = S(Kf)(SKK)? Bukankah seharusnya begitu λx.f(xx) = S(Kf)(SII) = S(Kf)(S(SKK)(SKK))? Saat mengkonversi λx.f(xx), saya mendapatkan S {λx.f} {λx.xx}pengurangan yang mana S (Kf) {λx.xx}dan ekspresi dalam tanda kurung tidak lain adalah ω=λx.xx, yang kita tahu direpresentasikan sebagai SII = S(SKK)(SKK), kan?
BarbaraKwarc

@ BarbaraKwarc Benar, maksud saya SII, tidak SKK. Itu adalah sebuah kesalahan.
Buah Esolanging

Jawaban:


9

Haskell, 251 237 222 214 byte

15 byte disimpan berkat @ Ørjan_Johansen (lihat juga tautan TIO-nya di komentar)!

8 byte lagi disimpan berkat @nimi!

data E=S|K|E:>E|V Int
p(h:s)|h>'_',(u,a)<-p s,(v,b)<-p u=(v,a:>b)|h>'['=a<$>p s|[(n,_:t)]<-reads s=(t,V n)
a(e:>f)=S:>a e:>a f
a(V 0)=S:>K:>K
a(V n)=K:>V(n-1)
a x=K:>x
o(e:>f)='`':o e++o f
o S="S"
o K="K"
f=o.snd.p

pmem-parsing input, mengembalikan bagian yang belum di-parsing di komponen pertama dari pasangan yang dihasilkan. Karakter pertama argumennya harus berupa backtick, backslash, atau bracket pembuka. Pola penjaga pmemeriksa kasus-kasus ini dalam urutan ini. Dalam kasus pertama, menunjukkan suatu aplikasi, dua ekspresi lagi diuraikan dan digabungkan ke elemen Etipe data dengan infix constructor :>. Dalam kasus lambda, ekspresi berikut diuraikan dan segera diberikan ke afungsi. Kalau tidak, ini adalah variabel, kita mendapatkan nomornya dengan readsfungsi (yang mengembalikan daftar) dan menjatuhkan braket penutup dengan pencocokan pola (_:t).

The afungsi melakukan braket abstraksi cukup terkenal. Untuk mengabstraksi suatu aplikasi, kami mengabstraksi dua sub-ekspresi dan menggunakan Skombinator untuk mendistribusikan argumen ke keduanya. Ini selalu benar, tetapi dengan lebih banyak kode kita bisa melakukan jauh lebih baik dengan menangani kasus khusus untuk mendapatkan ekspresi yang lebih pendek. Mengabstraksi variabel saat ini memberi Iatau, ketika kita tidak memilikinya SKK,. Biasanya case yang tersisa hanya dapat menambahkan a Kke depan, tetapi ketika menggunakan notasi ini kita harus memberi nomor baru variabel sebagai lambda bagian dalam diabstraksikan.

omengubah hasilnya menjadi string untuk output. fadalah fungsi yang lengkap.

Seperti dalam banyak bahasa, backslash adalah karakter pelarian, sehingga harus diberikan dua kali dalam string literal:

*Main> f "\\[0]"
"``SKK"
*Main> f "\\`[0][0]"
"``S``SKK``SKK"
*Main> f "\\\\[0]"
"``S``S`KS`KK`KK"
*Main> f "\\\\[1]"
"``S`KK``SKK"
*Main> f "\\\\\\``[2][0]`[1][0]"
"``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S`KK`KK``S`KK``SKK``S``S`KS``S``S`KS``S`KK`KS``S`KK`KK``S`KK`KK``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S`KK`KK``S``S`KS`KK`KK``S``S`KS``S``S`KS``S`KK`KS``S`KK`KK``S`KK`KK"

1
1. Di baris kedua, Anda bisa menggunakan (a,(b,v))<-p<$>p s. 2. '\\'Bisa saja _jika Anda memindahkan pertandingan terakhir.
Ørjan Johansen

1
Sebenarnya, gores bagian pertama: Lebih pendek untuk menukar urutan tuple dan digunakan p(_:s)=a<$>p suntuk garis (dipindahkan) '\\'.
Ørjan Johansen

1
Cobalah online! untuk versi Anda saat ini. Ngomong-ngomong, hanya 236 byte, Anda dapat membuang baris terakhir.
Ørjan Johansen

2
@ Challenger5 Saya pikir itu sebagian besar disebabkan oleh fakta bahwa haskell didasarkan pada lambda calculus, sehingga orang yang mahir dalam haskell lebih tertarik pada pertanyaan-pertanyaan seperti ini :)
Leo

2
Anda dapat menentukan pdengan ekspresi tunggal dengan tiga penjaga, mengatur ulang kasus dan drop sepasang berlebihan dari (): p(h:s)|h>'_',(u,a)<-p s,(v,b)<-p u=(v,a:>b)|h>'['=a<$>p s|[(n,_:t)]<-reads s=(t,V n).
nimi
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.