为无类型的lambda演算编写解释器


45

面临的挑战是为无类型lambda演算编写尽可能少的字符的解释器。我们将无类型的lambda演算定义如下:

句法

有以下三种表达式:

  • Lambda表达式的形式为(λ x. e),其中x可以是任何合法变量名和e任何合法表达式。这里x称为参数,e称为函数体。

    为简单起见,我们添加了进一步的限制,即不得存在与x当前作用域同名的变量。当变量的名称出现在和之间时,变量就开始在范围.内,而在对应的变量处就停止在范围内)

  • 功能应用程序具有形式和形式(f a),它们是合法的表达式。这里称为函数,称为参数。fafa
  • 变量的格式为xwhere x是合法变量名。

语义学

通过将函数体内参数的每次出现替换为其自变量来应用函数。更正式的形式的表达((λ x. e) a),其中x是变量名和ea是表达式,评估(或减少)至表达式e',其中e'是取代的每次出现的结果xea

范式是无法进一步评估的表达式。

挑战

您的任务(如果您选择接受它)是编写一个解释器,该解释器将不包含自由变量的未类型化lambda演算的表达式作为输入,并产生该表达式的标准形式(或与该表达式一致的表达式)作为其输出。 。如果该表达式没有范式或它不是有效的表达式,则该行为是不确定的。

字符数最少的解决方案获胜。

一些注意事项:

  • 输入既可以从stdin读取,也可以从作为命令行参数给出的文件名读取(您只需要实现一个或另一个即可-无需同时实现)。输出进入标准输出。
  • 或者,您可以定义一个函数,该函数将输入作为字符串,然后将输出作为字符串返回。
  • 如果非ASCII字符对您有问题,则可以使用反斜杠(\)字符代替λ。
  • 我们计算字符数,而不是字节数,因此,即使您的源文件被编码为unicodeλ也算为一个字符。
  • 合法变量名称由一个或多个小写字母组成,即a和z之间的字符(无需支持字母数字名称,大写字母或非拉丁字母-尽管这样做当然不会使您的解决方案无效)。
  • 就此挑战而言,没有括号是可选的。每个lambda表达式和每个函数应用程序都将恰好由一对括号包围。变量名不会用括号括起来。
  • 语法糖就像写(λ x y. e)(λ x. (λ y. e))并不需要得到支持。
  • 如果评估函数的递归深度大于100,则行为不确定。该值应该足够低,以至于无需在所有语言中进行优化即可实现,而且还应足够大以能够执行大多数表达式。
  • 您可能还假定间距将与示例中的相同,即在输入的开头和结尾或a λ或之前没有空格,而在a .和之后.以及函数与其参数之间以及a 之后恰好有一个空格λ

样本输入和输出

  • 输入: ((λ x. x) (λ y. (λ z. z)))

    输出: (λ y. (λ z. z))

  • 输入: (λ x. ((λ y. y) x))

    输出: (λ x. x)

  • 输入: ((λ x. (λ y. x)) (λ a. a))

    输出: (λ y. (λ a. a))

  • 输入: (((λ x. (λ y. x)) (λ a. a)) (λ b. b))

    输出: (λ a. a)

  • 输入: ((λ x. (λ y. y)) (λ a. a))

    输出: (λ y. y)

  • 输入: (((λ x. (λ y. y)) (λ a. a)) (λ b. b))

    输出: (λ b. b)

  • 输入: ((λx. (x x)) (λx. (x x)))

    输出:任何内容(这是没有标准格式的表达式的示例)

  • 输入: (((λ x. (λ y. x)) (λ a. a)) ((λx. (x x)) (λx. (x x))))

    输出:((λ a. a)这是一个表达式示例,如果您在函数调用之前对参数求值,则该表达式将不规范,而不幸的是,我尝试的解决方案失败的示例)

  • 输入: ((λ a. (λ b. (a (a (a b))))) (λ c. (λ d. (c (c d)))))

    输出:`(λ a. (λ b. (a (a (a (a (a (a (a (a b)))))))))) 这将计算教堂数字的2 ^ 3。


1
我们是否可以假定在字符串中没有前缀或附加空格,并且该空格在示例输入中已指定?也就是说,方括号之间,点和参数名称之间以及其他空白实例之间的空格都不是1个空格。
JPvdMerwe,2011年

@JPvdMerwe:是的,好点,您可以假设这一点。
sepp2k 2011年

是否有任何自由变量?我的意思是变量不受表达式中的lambda约束(\y. a)
FUZxxl 2011年

3
这里的许多或所有解决方案都无法实现避免捕获的替换!您应该添加一个测试用例(((λf。(λx。(fx)))(λy。(λx。y)))),其结果应为(λx。(λz。x)),不是(λx。(λx。x))。
Anders Kaseorg

1
@ sepp2k您是否考虑过将(((λf。(λx。(fx)))(λy。(λx.y))))添加为测试用例,但不接受当前错误地产生(λx。(λ x。x))?
Anders Kaseorg 2016年

Answers:


36

最新:

我将其压缩到644个字符,将部分cEll分解为cOpy和Par。将对单元格和cdr的调用缓存到临时局部变量中,并将这些局部变量移至“终端”(即非递归)函数中的全局变量中。此外,十进制常量比字符文字短,这令人讨厌的事情...

atom(x){
    return m[x]>>5==3;
}

...可以正确识别小写字母(假设为ASCII),但也可以接受`{|}〜中的任何一个。(在有关UTF-8的精彩视频中也有关于ASCII的相同观察。)

中提琴:|

#include<stdio.h>
#include<string.h>
#define X m[x]
#define R return
char*n,*m;int u,w,d;C(x,y){w=n-m;n+=sprintf(n,y?"(%s %s)":"(%s)",&X,m+y)+1;R w;}T(x){R X>>5==3;}
L(x){R X==92;}O(x,j){w=n-m;memcpy(n,&X,j);n+=j;*n++=0;R w;}E(x){X==' '?++x:0;R
X==41?0:L(x)?O(x,4):P(x);}P(x){d=0,w=x;do{X==40?d++:X==41?d--:0;++x;}while(d>0);R
O(w,x-w);}D(x){u=E(x+1);R u?E(x+1+strlen(m+u)):0;}V(x){int a=E(x+1),b=D(x);R
T(x)|T(a)?x:L(a)?C(a,V(b)):L(E(a+1))?V(S(V(b),E(a+3),D(a))):V(C(V(a),b?V(b):0));}S(w,y,x){R
T(x)?(X==m[y]?w:x):C(L(w+1)?E(x+1):S(w,y,E(x+1)),D(x)?S(w,y,D(x)):0);}
Y(char*s){n+=strlen(s=strcpy(n,s))+1;printf("%s\n%s\n\n",s,m+V(s-m));n=m+1;}

char*s[]={
"((\\ a. a) (b))",
"((\\ x. x) (\\ y. (\\ z. z)))",
"(\\ x. ((\\ y. y) x))",
"(((\\ x. (\\ y. x)) (\\ a. a)) (\\ b. b))",
"((\\ x. (\\ y. y)) (\\ a. a))",
"(((\\ x. (\\ y. y)) (\\ a. a)) (\\ b. b))",
"((\\x. (x x)) (\\x. (x x)))",0};
#include<unistd.h>
main(){char**k;n=m=sbrk(4096);*n++=0;for(k=s;*k;k++)Y(*k);R 0;}

之前:

我可以争取点票吗?我白天和黑夜都在工作一个星期。我挖出了麦卡锡(McCarthy)的原始论文,并为论文本身的错误所困扰,直到我阅读了保罗·格雷厄姆(Paul Graham)的《 Lisp的根源》的附录。我太分心了,以至于我将自己锁在屋外,然后完全忘记了,直到那天晚上12:30再次回到家(有点晚才打电话给住在县里的建筑经理),不得不花晚上在祖母家(偷东西直到笔记本电池没电了)。

毕竟,它甚至与获奖作品还差得远!

我不确定如何使它更短。而且我用了我能想到的所有肮脏的把戏!也许它不能用C语言完成。

有了一些慷慨的计算(第一个块采用一个字符串并打印出结果),它就是778 770 709 694个字符。但是要使其独立,它必须具有该sbrk调用。为了处理更复杂的表达式,它也需要signal处理程序。当然,它不能与任何尝试使用的代码一起制成模块malloc

所以,a,这里是:

#include<stdio.h>
#include<string.h>
#define K(j) strncpy(n,m+x,j);n+=j;goto N;
#define R return
#define X m[x]
#define L =='\\'
char*m,*n;T(x){R islower(X);}V(x){int a=E(x+1);R
T(x)?x:T(a)?x:m[a]L?C(a,V(D(x))):m[E(a+1)]L?V(S(V(D(x)),E(a+3),D(a))):V(C(V(a),D(x)?V(D(x)):0));}
C(x,y){char*t=n;sprintf(n,y?"(%s %s)":"(%s)",m+x,m+y);n+=strlen(n)+1;R
t-m;}Y(char*s){char*t=strcpy(n,s);n+=strlen(n)+1;printf("%s=>%s\n",s,m+V(t-m));n=m+1;}S(x,y,z){R
T(z)?(m[z]==m[y]?x:z):C(m[z+1]L?E(z+1):S(x,y,E(z+1)),D(z)?S(x,y,D(z)):0);}D(x){R
E(x+1)?E(x+strlen(m+E(x+1))+1):0;}E(x){char*t=n,d=0;if(X==' ')++x;if(T(x)){K(1)}if(X
L){K(4)}do{d=X?(X=='('?d+1:(X==')'?d-1:d)):0;*n++=m[x++];}while(d);N:*n++=0;R t-m;}

char*samp[]={
    "a","a","b","b",
    "((\\ a. a) (b))", "(b)",
    "((\\ x. x) (\\ y. (\\ z. z)))", "(\\ y. (\\ z. z))",
    "(\\ x. ((\\ y. y) x))", "(\\ x. x)",
    "(((\\ x. (\\ y. x)) (\\ a. a)) (\\ b. b))", "(\\ a. a)",
    "((\\ x. (\\ y. y)) (\\ a. a))", "(\\ y. y)",
    "(((\\ x. (\\ y. y)) (\\ a. a)) (\\ b. b))", "(\\ b. b)",
    "((\\x. (x x)) (\\x. (x x)))", "undef",
    NULL};
#include<unistd.h>

unsigned sz;
#include<signal.h>
void fix(x){signal(SIGSEGV,fix);brk(m+(sz*=2));}
main(){
    char**t;
    signal(SIGSEGV,fix);
    m=n=sbrk(sz=10*getpagesize());
    *n++=0;
    for(t=samp;*t;t+=2){
        Y(*t);
        printf("s.b. => %s\n\n", t[1]);
    }
    return 0;
}

这是最终削减之前的区块。这里的技巧是使用整数游标而不是指针(利用“隐式int”行为),以及使用“暂存”:char*n即是指向可用空间的“新”或“下一个”指针。但是有时我将一个字符串写入内存,然后调用strlen并递增n;在更容易计算大小之后,有效地使用内存然后进行分配。您可以从McCarthy的论文中直接看到它,除了cell()函数和数据的字符串表示形式之间的哪些接口。

#include<stdio.h>
#include<string.h>
char*m,*n;  //memory_base, memory_next
atom(x){  // x is an atom if it is a cursor to a lowercase alpha char.
    return x?(islower(m[x])?m[x]:0):0;
}
eq(x,y){  // x and y are equal if they are both atoms, the same atom.
    return x&&y&&atom(x)==atom(y);
}
cell(x){  // return a copy of the list-string by cursor, by parsing
    char*t=n,d=0;
    if(!x||!m[x])
        return 0;
    if(m[x]==' ')
        ++x;
    if(atom(x)){
        *n++=m[x];
        *n++=0;
        return(n-m)-2;
    }
    if(m[x]=='\\'){  // our lambda symbol
        memcpy(n,m+x,4);
        n+=4;
        *n++=0;
        return(n-m)-5;
    }
    do{  // um ...
        d=m[x]?(m[x]=='('?d+1:(m[x]==')'?d-1:d)):0;
        *n++=m[x++];
    }while(d);
    *n++=0;
    return t-m;
}
car(x){  // return (copy of) first element
    return x?cell(x+1):0;
}
cdr(x){  // return (copy of) rest of list
    return car(x)?cell(x+strlen(m+car(x))+1):0;
}
cons(x,y){  // return new list containing first x and rest y
    char*t=n;
    return x?(sprintf(n,y?"(%s %s)":"(%s)",m+x,m+y),n+=strlen(n)+1,t-m):0;
}
subst(x,y,z){  // substitute x for z in y
    if(!x||!y||!z)
        return 0;
    return atom(z)? (eq(z,y)?x:z):
        cons(m[z+1]=='\\'?car(z):
        subst(x,y,car(z)),cdr(z)?subst(x,y,cdr(z)):0);
}
eval(x){  // evaluate a lambda expression
    int a;
    return atom(x)?x:
        atom(a=car(x))?x:
        m[a]=='\\'?cons(a,eval(cdr(x))):
        m[car(a)]=='\\'?eval(subst(eval(cdr(x)),cell(a+3),cdr(a))):
        eval( cons(eval(a),cdr(x)?eval(cdr(x)):0));
}
try(char*s){  // handler
    char*t=strcpy(n,s);
    n+=strlen(n)+1;
    printf("input: %s\n", s);
    printf("eval => %s\n", m+eval(t-m));
    n=m+1;
}

1
我发现了一些保存一个或两个字符的窍门,但没有什么大不了的。sprintf(n,...);n+=strlen(n)+1;更好,因为n+=sprintf(n,...)+1;反转数组语法x[m]而不是m[x]让我用'postfix'宏替换所有间接寻址#define M [m]... x M可以节省1个字符并提供“自由”换行符,因为必须使用空格来分隔标记。
luser droog 2011年

似乎与IOCCC 1989的 jar.2 xlisp 4.0有一些相似之处。
luser droog

我试图将其扩展为更完整的Lisp解释器
luser droog

注释的代码// um ...将遍历字符串并计算括号,直到在正确的嵌套级别找到匹配的近亲。
luser droog 2015年

1
这会错误地将((\ f。(\ x。(fx)))(\ y。(\ x。y)))评估为(\ x。(fx)))。
Anders Kaseorg

22

二进制Lambda微积分186

下面的十六进制转储中显示的程序

00000000  18 18 18 18 18 18 44 45  1a 10 18 18 45 7f fb cf  |......DE....E...|
00000010  f0 b9 fe 00 78 7f 0b 6f  cf f8 7f c0 0b 9f de 7e  |....x..o.......~|
00000020  f2 cf e1 b0 bf e1 ff 0e  6f 79 ff d3 40 f3 a4 46  |........oy..@..F|
00000030  87 34 0a a8 d0 80 2b 0b  ff 78 16 ff fe 16 fc 2d  |.4....+..x.....-|
00000040  ff ff fc ab ff 06 55 1a  00 58 57 ef 81 15 bf bf  |......U..XW.....|
00000050  0b 6f 02 fd 60 7e 16 f7  3d 11 7f 3f 00 df fb c0  |.o..`~..=..?....|
00000060  bf f9 7e f8 85 5f e0 60  df 70 b7 ff ff e5 5f f0  |..~.._.`.p...._.|
00000070  30 30 6f dd 80 5b b3 41  be 85 bf ff ca a3 42 0a  |00o..[.A......B.|
00000080  c2 bc c0 37 83 00 c0 3c  2b ff 9f f5 10 22 bc 03  |...7...<+...."..|
00000090  3d f0 71 95 f6 57 d0 60  18 05 df ef c0 30 0b bf  |=.q..W.`.....0..|
000000a0  7f 01 9a c1 70 2e 80 5b  ff e7 c2 df fe e1 15 55  |....p..[.......U|
000000b0  75 55 41 82 0a 20 28 29  5c 61                    |uUA.. ()\a|
000000ba

不完全接受您建议的格式。相反,它期望二进制lambda演算(blc)格式的lambda项。但是,它确实使用最小的括号显示了正常形式简化中的每个步骤。

示例:计算教堂数字中的2 ^ 3

使用xxd -r> symbolic.Blc保存上述十六进制转储

http://tromp.github.io/cl/uni.c获取 blc解释器

cc -O2 -DM=0x100000 -m32 -std=c99 uni.c -o uni
echo -n "010000011100111001110100000011100111010" > threetwo.blc
cat symbolic.Blc threetwo.blc | ./uni
(\a \b a (a (a b))) (\a \b a (a b))
\a (\b \c b (b c)) ((\b \c b (b c)) ((\b \c b (b c)) a))
\a \b (\c \d c (c d)) ((\c \d c (c d)) a) ((\c \d c (c d)) ((\c \d c (c d)) a) b)
\a \b (\c (\d \e d (d e)) a ((\d \e d (d e)) a c)) ((\c \d c (c d)) ((\c \d c (c d)) a) b)
\a \b (\c \d c (c d)) a ((\c \d c (c d)) a ((\c \d c (c d)) ((\c \d c (c d)) a) b))
\a \b (\c a (a c)) ((\c \d c (c d)) a ((\c \d c (c d)) ((\c \d c (c d)) a) b))
\a \b a (a ((\c \d c (c d)) a ((\c \d c (c d)) ((\c \d c (c d)) a) b)))
\a \b a (a ((\c a (a c)) ((\c \d c (c d)) ((\c \d c (c d)) a) b)))
\a \b a (a (a (a ((\c \d c (c d)) ((\c \d c (c d)) a) b))))
\a \b a (a (a (a ((\c (\d \e d (d e)) a ((\d \e d (d e)) a c)) b))))
\a \b a (a (a (a ((\c \d c (c d)) a ((\c \d c (c d)) a b)))))
\a \b a (a (a (a ((\c a (a c)) ((\c \d c (c d)) a b)))))
\a \b a (a (a (a (a (a ((\c \d c (c d)) a b))))))
\a \b a (a (a (a (a (a ((\c a (a c)) b))))))
\a \b a (a (a (a (a (a (a (a b)))))))

由于hexdump相当不可读,因此这里是“反汇编”版本

@10\\@10\\@10\\@10\\@10\\@10\@\@\@\@@\@1010\@\\\@10\\@10\@\@@@1111111111101
1110@11111110\@@110@11111110\\\\@1110\@1111110\@@101101111110@111111110\@111
111110\\\\@@110@111111011110@11111011110@@10@1111110\@10110\@@111111110\@111
111110\@110@101111011110@1111111111010@1010\\@1110@11010@\@\@1010\@110@1010\
\@@@@@\@1010\@\\\\@@@10\@@111111111011110\\@@101111111111111110\@@101111110\
@@10111111111111111111111110@@@@1111111110\\110@@@@\@1010\\\\@@10\@@@1111101
11110\\@\@@@10111111101111110\@@1011011110\\@@11111010110\\@111110\@@1011110
1110@111010\10\1011111110@111110\\\@101111111111011110\\@@11111111110@@11111
0111110\10\@@@@11111110\\@10\\1101111101110\@@1011111111111111111111110@@@@1
11111110\\@10\\@10\\11011111101110110\\\@@101110110@1010\\11011111010\@@1011
111111111111110@@@@\@1010\@\\@@@10\@@@1110@10\\\@1011110\\110\\\@10\\\@1110\
@@@11111111110@1111111101010\10\\@\@@@1110\\\@10@1110111110\\1110\110@@@1111
0110@@@1111010\\110\\\@10\\\@@1101111111101111110\\\@10\\\@@1101111110111111
10\\\110@1010110\\101110\\@@11010\\\@@1011111111111110@11110\@@1011111111111
101110\@\@@@@@@@@11010101010101010\\110\\10\\1010\10\\\1010\\1010@@@110\110\
@

用\替换00(lambda)并用@替换01(application)现在几乎像Brainfuck一样可读:-)

另请参阅http://www.ioccc.org/2012/tromp/hint.html


7
BLC恰好使用二进制字母。00是lambda,01是应用程序,1 ^ {n} 0是一元变量。不涉及任何编译。
John Tromp 2012年

3
您在哪里获得因子x3?您实际上提出了一个很好的观点,即使用源字母较小的语言(如BF)会受到惩罚。为了公平比较,所有大小都应以位表示,并且BF字符每个仅占用3位。大多数其它语言需要7位对ASCII,一些使用所有8
约翰·特朗普

1
BTW +1这太酷了!
luser droog 2012年

1
如果fractran中的fractran是可以接受的,我根本不明白为什么这应该是一个问题。你看不懂吗 你想要?学习!
luser droog 2012年

1
要使其读入实际的输入格式需要什么?我认为那是您失去潜在投票的地方。
luser droog 2013年

14

Haskell中,342个 323 317 305字符

在撰写本文时,这是唯一可评估((λf。(λx。(fx)))(λy。(λx。y)))以得出正确结果(λx。(λz。 x)),而不是(λx。(λx。x))。lambda演算的正确实现需要避免捕获,即使在此问题的简化保证下,也没有变量会覆盖范围内的另一个变量。(即使没有此保证,我的程序也能正常工作。)

data T=T{a::T->T,(%)::ShowS}
i d=T(i. \x v->'(':d v++' ':x%v++")")d
l f=f`T`\v->"(λ "++v++". "++f(i(\_->v))%('x':v)++")"
(?)=q.lex
q[(v,s)]k|v/="("=k(maybe T{}id.lookup v)s|'λ':u<-s,[(w,_:t)]<-lex u=t? \b->k(\e->l$b.(:e).(,)w).tail|0<1=s? \f->(?(.tail).k. \x z->f z`a`x z)
main=interact(? \f->(f[]%"x"++))

笔记:

  • 它按要求在GHC 7.0中运行,因为此挑战是在2011年1月提出的。如果允许我假设GHC 7.10 ,它将短13个字符

带文档的非高尔夫版本


您在ideone haskell编译器中的编入到输入((\\ x.x)(\ y。(\ z.z)))中,即使在((\\ x。x)(\\ y。( \\ z。z)))...在Haskell中是什么意思?
RosLuP

2
@RosLuP我的程序接受λ,而不接受\。
Anders Kaseorg '16

在ideone.com中键入此归因((λx。x)(λy。(λz。z)))返回:运行时错误时间:0存储器:4876信号:-1
RosLuP

1
@RosLuP Ideone似乎已破坏了Unicode支持。尝试使用命令行或其他联机解释器(例如,它在Rextester有效)。
Anders Kaseorg

2
@codeshot问题作者已经评论说((λf 。(λx 。(fx)))(λy 。(λx。y )))↦(λx。(λz。x))对于这个问题(就像真正的lambda演算一样)。
Anders Kaseorg '18

13

蟒蛇- 321 320

这是我的(固定)尝试:

l="("
def S(s):
 if s[0]!=l:return s
 if s[1]=="\\":g=s.find('.');return"(\\ %s. %s)"%(s[3:g],S(s[g+2:-1]))
 i=2;c=s[1]==l
 while c:c+=(s[i]==l)-(s[i]==')');i+=1
 t=S(s[1:i])
 z=s[i+1:-1]
 if l!=t[0]:return"(%s %s)"%(t,S(z))
 g=t.find('.')
 t=S(t[g+2:-1]).replace(t[3:g],z)
 if t!=s:t=S(t)
 return t
print S(raw_input())

这看起来不错,但似乎不起作用。我添加了一些示例输入和输出,它们的代码会产生错误的结果。
sepp2k 2011年

1
这无法进行避免捕获的替换。例如,((\ f。(\ x。(fx)))(\ y。(\ x。y)))对(\ x。(\ x。x))的评估不正确。
Anders Kaseorg

1
为什么当它几乎无法正常工作时,将其标记为答案?您是否尝试过作者给定的输入和输出?
rbaleksandar

1
作者提供的测试用例不足以证明此答案中的错误。
Anders Kaseorg '16

1
这个答案既不正确也不短。它无法避免捕获,并且具有字符串替换错误。
理查德·帕德利

6

Ruby 254个字符

f=->u,r{r.chars.take_while{|c|u+=c==?(?1:c==?)?-1:0;u>0}*''}
l=->x{x=~/^(\(*)\(\\ (\w+)\. (.*)/&&(b,v,r=$1,$2,$3;e=f[1,r];(e==s=l[e])?b==''?x:(s=f[2,r];(x==y=b.chop+e.gsub(v,s[2+e.size..-1])+r[1+s.size..-1])?x:l[y]):(b+'(\\ '+v+'. '+s+r[e.size..-1]))||x}

它可以像

puts l["((\\ x. (\\ y. x)) (\\ a. a))"]    # <= (\ y. (\ a. a))

该解决方案尚未完全解决,但已经几乎无法理解。


你好羡慕,我的老朋友:)
luser droog 2011年

这无法进行避免捕获的替换。例如,((\ f。(\ x。(fx)))(\ y。(\ x。y)))对(\ x。(\ x。x))的评估不正确。
Anders Kaseorg

除了上述捕获错误外,这还会错误地将(\ y。(\ xx。((\ x。xx)y)))评估为(\ y。(\ xx。yy)),在此情况下会产生过度的字符串替换不存在的变量yy。
Anders Kaseorg

3

编辑:在纯JavaScript下检查以下我的答案是否为250。

使用LiveScript的2852 243个字符(不使用正则表达式!未完全打高尔夫-可以改进)

L=(.0==\\)
A=->it.forEach?&&it.0!=\\
V=(.toFixed?)
S=(a,b,t=-1,l=0)->|L a=>[\\,S(a.1,b,t,l+1)];|A a=>(map (->S(a[it],b,t,l)),[0 1]);|a==l+-1=>S(b,0,l+-1,0)||a|l-1<a=>a+t;|_=>a
R=(a)->|L a=>[\\,R a.1]|(A a)&&(L a.0)=>R(S(R(a.0),R(a.1)).1)|_=>a

测试:

a = [\\,[\\,[1 [1 0]]]]
b = [\\,[\\,[1 [1 [1 0]]]]]
console.log R [a, b]
# outputs ["\\",["\\",[1,[1,[1,[1,[1,[1,[1,[1,[1,0]]]]]]]]]]]

这是3^2=9,作为OP说。

如果有人好奇,这里是带有一些注释的扩展版本:

# Just type checking
λ = 100
isλ = (.0==λ)
isA = -> it.forEach? && it.0!=λ
isV = (.toFixed?)

# Performs substitutions in trees
# a: trees to perform substitution in
# b: substitute bound variables by this, if != void
# f: add this value to all unbound variables
# l: internal (depth)
S = (a,b,t=-1,l=0) ->
    switch
    | isλ a             => [λ, (S a.1, b, t, l+1)]
    | isA a             => [(S a.0, b, t, l), (S a.1, b, t, l)]
    | a == l - 1        => (S b, 0, (l - 1), 0) || a
    | l - 1 < a < 100   => a + t
    | _                 => a

# Performs the beta-reduction
R = (a) ->
    switch
    | (isλ a)               => [λ,R a.1]
    | (isA a) && (isλ a.0)  => R(S(R(a.0),R(a.1)).1)
    | _                     => a

# Test
a = [λ,[λ,[1 [1 0]]]]
b = [λ,[λ,[1 [1 [1 0]]]]]
console.log show R [a, b]

这与问题的输入和输出规范不符。
Anders Kaseorg 2015年

3

Waterhouse Arc-140个字符

(=
f[is cons?&car._'λ]n[if
atom._ _
f._ `(λ,_.1,n:_.2)(=
c n:_.0
e _)(if
f.c(n:deep-map[if(is
c.1 _)e.1
_]c.2)(map n
_))]λ[n:read:rem #\._])

我在哪里可以买到Waterhouse Arc?
Anders Kaseorg

1
找不到有效的口译员

@AndersKaseorg 此处
仅ASCII的

仅@ASCII,我知道Arc是什么,但是“ Waterhouse”部分向我建议某些特殊的方言是必需的。你让它运行了吗?
安德斯·卡塞格

@AndersKaseorg没关系。找到了它
仅ASCII

2

C 1039字节

#define F for
#define R return
#define E if(i>=M||j>=M)R-1;
enum{O='(',C,M=3999};signed char Q[M],D[M],t[M],Z,v,*o=Q,*d=D,*T;int m,n,s,c,w,x,y;K(i,j,k){!Z&&(Z=t[O]=1)+(t[C]=-1);E;if(!o[i]){d[j]=0;R 0;}if((c=t[o[i]]+t[o[i+1]])!=2||o[i+2]!='\\'){d[j++]=o[i++];R K(i,j,i);}F(i+=2,y=w=0;i<M&&o[i]&&c;++i)c+=t[o[i]],!w&&c==1?w=i:0,!y&&o[i]=='.'?y=i+2:0;E;if(c){F(;d[j++]=o[i++];)E;R 0;}F(c=y;c<w;++c)if(o[c]=='\\')F(n=0,m=w+2;m<i;++m){if(o[m]==o[c+2]){F(x=0;o[m+x]&&isalpha(o[m+x])&&o[m+x]==o[c+2+x];++x);if(o[c+2+x]!='.'||isalpha(o[m+x]))continue;if(v>'Z')R-1;F(n=c+2;n<w;++n)if(o[n]==o[m]){F(x=0; o[m+x]&&isalpha(o[m+x])&&o[m+x]==o[n+x];++x);if(o[m+x]=='.'&&!isalpha(o[n+x]))F(;--x>=0;) o[n+x]=v;}++v;}}F(c=y;c<w&&j<M;++c){F(x=0;o[c+x]&&o[c+x]==o[k+4+x]&&isalpha(o[c+x]); ++x);if(o[k+4+x]=='.'&&!isalpha(o[c+x])){F(m=w+2;m<i-1&&j<M;++m)d[j++]=o[m];c+=x-1;}else d[j++]=o[c];}E;Z=2;R K(i,j,i);}char*L(char*a){F(s=n=0;n<M&&(o[n]=a[n]);++n);if(n==M)R 0;v='A';F(;++s<M;){Z=0;n=K(0,0,0);if(Z==2&&n!=-1)T=d,d=o,o=T;else break;}R n==-1||s>=M?0:d;}

变量允许使用小写字母[来自a..z]作为输入,如果输出需要,则sys可以使用大写字母[a..Z]生成变量...假定为ascii字符配置。

#define P printf
main()
{char  *r[]={ "((\\ abc. (\\ b. (abc (abc (abc b))))) (\\ cc. (\\ dd. (cc (cc dd)))))",
              "((\\ fa. (\\ abc. (fa abc))) (\\ yy. (\\ abc. yy)))",
              "((\\ x. x) z)", 
              "((\\ x. x) (\\ y. (\\ z. z)))", 
              "(\\ x. ((\\ y. y) x))", 
              "((\\ x. (\\ y. x)) (\\ a. a))", 
              "(((\\ x. (\\ y. x)) (\\ a. a)) (\\ b. b))",
              "((\\ x. (\\ y. y)) (\\ a. a))",
              "(((\\ x. (\\ y. y)) (\\ a. a)) (\\ b. b))",             
              "((\\ x. (x x)) (\\ x. (x x)))",
              "(((\\ x. (\\ y. x)) (\\ a. a)) ((\\ x. (x x)) (\\ x. (x x))))",
             0}, *p;
 int    w;

 for(w=0; r[w] ;++w)
   {p=L(r[w]);
    P("o=%s d=%s\n", r[w], p==0?"Error ":p);
   }
 R  0;
}

/*1.039*/

规范要求\或λ,而不是/。它还需要支持多字母变量名。
Anders Kaseorg

'\ n'等符号'\'还有其他用途,最好改用'/'
RosLuP

1
尽管如此,挑战仍然是满足规范,而不是使其更好。
Anders Kaseorg

我写了一些东西,因为它更具一致性……但是大小爆炸了……
RosLuP


1

哈斯克尔456 C

如果充分利用Haskell的惰性评估功能,则可能会短得多。可悲的是,我不知道该怎么做。

同样,在解析步骤中浪费了许多字符。

data T=A[Char]|B[Char]T|C T T
(!)=(++)
s(A a)=a
s(B a b)="(λ "!a!". "!s b!")"
s(C a b)='(':s a!" "!s b!")"
e d(A a)=maybe(A a)id(lookup a d)
e d(B a b)=B a.e d$b
e d(C a b)=f d(e d a)(e d b)
f d(B x s)q=e((x,q):d)s
f d p q=C p q
d=tail
p('(':'λ':s)=let(A c,t)=p(d s);(b,u)=p(d.d$t);in(B c b,d u)
p('(':s)=let(a,t)=p s;(b,u)=p(d t)in(C a b,d u)
p(c:s)|elem c" .)"=(A "",c:s)|1<2=let((A w),t)=p s in(A(c:w),t)
r=s.e[].fst.p
main=do l<-getLine;putStrLn$r l

非高尔夫版本

data Expression = Literal String 
                | Lambda String Expression
                | Apply Expression Expression
                deriving Show

type Context = [(String, Expression)]

show' :: Expression -> String
show' (Literal a) = a
show' (Lambda x e) = "(λ " ++ x ++ ". " ++ show' e ++ ")"
show' (Apply e1 e2) = "(" ++ show' e1 ++ " " ++ show' e2 ++ ")"

eval :: Context -> Expression -> Expression
eval context e@(Literal a) = maybe e id (lookup a context)
eval context (Lambda x e) = Lambda x (eval context e)
eval context (Apply e1 e2) = apply context (eval context e1) (eval context e2)

apply :: Context -> Expression -> Expression -> Expression
apply context (Lambda x e) e2 = eval ((x, e2):context) e
apply context e1 e2 = Apply e1 e2

parse :: String -> (Expression, String)
parse ('(':'λ':s) = let
    (Literal a, s') = parse (tail s)
    (e, s'') = parse (drop 2 s')
    in (Lambda a e, tail s'')

parse ('(':s) = let
    (e1, s') = parse s
    (e2, s'') = parse (tail s')
    in (Apply e1 e2, tail s'')

parse (c:s) | elem c " .)" = (Literal "", c:s)
            | otherwise    = let ((Literal a), s') = parse s 
                             in (Literal (c:a), s')

run :: String -> String
run = show' . eval [] . fst . parse
main = do
  line <- getLine
  putStrLn$ run line

3
这无法进行避免捕获的替换。例如,(((λf。(λx。(fx)))(λy。(λx。y)))对(λx。(λx。x))的评估不正确。
Anders Kaseorg

1

使用JavaScript获得了231 /没有正则表达式

(function f(a){return a[0]?(a=a.map(f),1===a[0][0]?f(function d(b,a,e,c){return b[0]?1===b[0]?[1,d(b[1],a,e,c+1)]:2===b[0]?b[1]===c-1?d(a,0,c-1,0)||b:c-1<b[1]?[2,b[1]+e]:b:[d(b[0],a,e,c),d(b[1],a,e,c)]:b}(a[0],a[1],-1,0)[1]):a):a})

接收2个元素的数组。1表示λ和图2表示一个Bruijn的索引变量。

测试:

zero = [1,[1,[2,0]]]; // λλ0
succ = [1,[1,[1,[[2,1],[[[2,2],[2,1]],[2,0]]]]]]; // λλλ(1 ((2 1) 0))
console.log(JSON.stringify(reduce([succ,[succ,[succ,zero]]]))); // 0+1+1+1
// Output: [1,[1,[[2,1],[[2,1],[[2,1],[2,0]]]]]] = λλ(1(1(1 0))) = number 3

这与问题的输入和输出规范不符。
Anders Kaseorg 2015年

1

Python:1266个字符(使用wc测量)

from collections import *;import re
A,B,y,c=namedtuple('A',['l','r']),namedtuple('B',['i','b']),type,list.pop
def ab(t):c(t,0);p=c(t,0);c(t,0);return B(p,tm(t))
def tm(t):return ab(t)if t[0]=='\\'else ap(t)
def at(t):
    if t[0]=='(':c(t,0);r=tm(t);c(t,0);return r
    if 96<ord(t[0][0])<123:return c(t,0)
    if t[0]=='\\':return ab(t)
def ap(t):
    l = at(t)
    while 1:
        r = at(t)
        if not r:return l
        l = A(l,r)
def P(s):return tm(re.findall(r'(\(|\)|\\|[a-z]\w*|\.)',s)+['='])
def V(e):o=y(e);return V(e.b)-{e.i} if o==B else V(e.l)|V(e.r)if o==A else{e}
def R(e,f,t):return B(e.i,R(e.b,f,t)) if y(e)==B else A(R(e.l,f,t),R(e.r,f,t))if y(e)==A else t if e==f else e
def N(i,e):return N(chr(97+(ord(i[0])-96)%26),e) if i in V(e)else i
def S(i,e,a): return A(S(i,e.l,a),S(i,e.r,a)) if y(e)==A else(e if e.i==i else B(N(e.i,a),S(i,R(e.b,e.i,N(e.i,a)),a)))if y(e)==B else a if e==i else e
def T(e):
    if y(e)==A:l,r=e;return S(l.i,l.b,r)if y(l)==B else A(T(l),r)if y(l)==A else A(l,T(r))
    if y(e)==B:return B(e.i,T(e.b))
    q
def F(e):o=y(e);return r'(\%s. %s)'%(e.i,F(e.b))if o==B else'(%s %s)'%(F(e.l),F(e.r)) if o==A else e
def E(a):
    try: return E(T(a))
    except NameError:print(F(a))
E(P(input()))

这不是一个短距离的远射,但它可以正确处理alpha重命名以及OP帖子中列出的所有示例。


您可以缩短其中一些函数名称,并将其中一些转换为lambda。您到处也有一些多余的空白
Jo King

(1)用一个空格替换4个空格的缩进将节省很多字节。(2)你可以替换except NameErrorexcept?(3)两个字符的函数名称可以重命名为一个字符的名称。(4)在某些地方,您有分配作业的地方,的周围有空格=。(5)if t[0]=='c'可以替换为if'c'==t[0]
硕果累累

通过大多数格式更改(例如缩进和lambda)来更改1045个字节
Jo King

0

C ++(gcc)782 766 758 731个字节

#include <string>
#include <map>
#define A return
#define N new E
using S=std::string;using C=char;using I=int;S V(I i){A(i>8?V(i/9):"")+C(97+i%9);}S W(C*&s){C*b=s;while(*++s>96);A{b,s};}struct E{I t,i;E*l,*r;E(E&o,I d,I e){t=o.t;i=o.i+(o.i>=d)*e;t?l=N{*o.l,d,e},t-1?r=N{*o.r,d,e}:0:0;}E(I d,std::map<S,I>m,C*&s){t=*s-40?i=m[W(s)],0:*++s-92?l=N{d,m,s},r=N{d,m,++s},++s,2:(m[W(s+=2)]=d,l=N{d+1,m,s+=2},++s,1);}I R(I d){A t?t-1?l->t==1?l->l->s(d,0,*r),*this=*l->l,1:l->R(d)||r->R(d):l->R(d+1):0;}I s(I d,I e,E&v){t?t-1?l->s(d,e,v),r->s(d,e,v):l->s(d,e+1,v):i==d?*this={v,d,e},0:i-=i>d;}S u(I d){A t?t-1?S{"("}+l->u(d)+' '+r->u(d)+')':S{"(\\ "}+V(d)+". "+l->u(d+1)+')':V(i);}};S f(C*s){E a{0,{},s};for(I c=999;a.R(0)&&c--;);A a.u(0);}

在线尝试!

这里的基本思想是,代码使用基于de Bruijn索引思想的内部表示形式-除了我将索引反转以指示所引用变量绑定的lambda深度外。在代码中:

  • E::t表示节点的类型-可变叶节点为0,lambda节点为1,功能应用程序节点为2。(选择它使其与节点的Arity一致,这恰好是可能的。)然后E::lE::r适当地将其作为子E::l节点(仅用于lambda节点),并且E::i是可变叶节点的lambda-depth索引。
  • 构造函数会E::E(E&o,int d,int e)克隆一个最初位于lambda-depth的子表达式,d以便粘贴到lambda-depth 的新位置d+e。这涉及到在λ深度处保留的变量少于d在λ深度处将变量递增至少d的情况e
  • E::s将子表达式替换为in中的v变量d*this同时减小大于的变量d(并且e是内部细节,用于跟踪何时需要调用的lambda深度增量E::c)。
  • E::R搜索要执行的单个beta归约,根据通过AST进行的预搜索,优先选择最顶部或最左侧的实例。如果发现执行减少,则返回非零;否则,返回零。
  • E::u是一种to_string类型操作,它使用变量的综合名称来重构“人类可读”字符串。(请注意,由于对V辅助功能的了解不多,因此只会生成包含a通过的名称i。)
  • 构造函数根据当前绑定的变量名到lambda深度索引的映射E::E(int d, std::map<std::string, int> m, char*&s),将输入字符串解析为sAST表达式m
  • f 是回答问题的主要功能。

(正如您在TIO链接上所看到的那样,该代码确实处理具有多个字符的变量名,并且它也获得了(\ a. (\ b. a))for 的正确答案((\ f. (\ x. (f x))) (\ y. (\ x. y)))。这恰好也是因为解析代码可以免费处理变量阴影。)


-16个字节,部分是由于ceilingcat的想法(我也已经独立提出),部分是由于更改E*a=new E;E&a=*new E;,然后更改a->a.

多了-8个字节,这是由于ceilingcat的另一条注释(a.t三元组的输出分配)

-27字节,来自将解析器和克隆转换为的构造函数 E


-1

C 637字节

#define R return
#define E if(i>=M||j>=M)R-1;
#define H d[j++]
enum{O=40,C,M=3999};signed char Q[M],D[M],t[M],Z,*o=Q,*d=D,*T;int m,n,s,c,w;K(i,j,k){!Z&&(Z=t[O]=1)+(t[C]=-1);E;if(!o[i]){H=0;R 0;}if((c=t[o[i]]+t[o[i+1]])!=2||o[i+2]!=92){H=o[i++];R K(i,j,i);}for(i+=2,w=0;i<M&&o[i]&&c;++i)c+=t[o[i]],!w&&c==1?w=i:0;E;if(c){for(;H=o[i++];)E;R 0;}for(c=k+7,n=j;c<w&&j<M;++c)if(o[c]==o[k+4]){if(o[c+1]==46){d[n++]=o[k++];R K(k,n,k);}for(m=w+2;m<i-1&&j<M;)H=o[m++];}else H=o[c];E;Z=2;R K(i,j,i);}char*L(char*a){for(s=n=0;n<M&&(o[n]=a[n]);++n);if(n==M)R 0;for(;++s<M;){Z=0;if((n=K(0,0,0))!=-1&&Z==2)T=d,d=o,o=T;else break;}R n==-1||s>=M?0:d;}

此版本不使用辅助变量(因此不遵循lambda演算所说的100%...此处的其他数量...)。每个变量的长度必须为1个字符(与此处的其他变量一样)。测试代码:

#define P printf

main()
{char  *r[]={ "((\\ x. x) z)", 
              "((\\ x. x) (\\ y. (\\ z. z)))", 
              "(\\ x. ((\\ y. y) x))", 
              "((\\ x. (\\ y. x)) (\\ a. a))", 
              "(((\\ x. (\\ y. x)) (\\ a. a)) (\\ b. b))",
              "((\\ x. (\\ y. y)) (\\ a. a))",
              "(((\\ x. (\\ y. y)) (\\ a. a)) (\\ b. b))",
              "((\\ x. (x x)) (\\ x. (x x)))",
              "(((\\ x. (\\ y. x)) (\\ a. a)) ((\\ x. (x x)) (\\ x. (x x))))",
              "((\\ a. (\\ b. (a (a (a b))))) (\\ c. (\\ d. (c (c d)))))",
              "((\\ f. (\\ x. (f x))) (\\ y. (\\ x. y)))",
             0}, *y;
 int    w;

 for(w=0; r[w] ;++w)
   {y=L(r[w]);
    P("o=%s d=%s\n", r[w], y==0?"Error ":y);
   }
 R  0;
}

结果:

/*
637
o=((\ x. x) z) d=z
o=((\ x. x) (\ y. (\ z. z))) d=(\ y. (\ z. z))
o=(\ x. ((\ y. y) x)) d=(\ x. x)
o=((\ x. (\ y. x)) (\ a. a)) d=(\ y. (\ a. a))
o=(((\ x. (\ y. x)) (\ a. a)) (\ b. b)) d=(\ a. a)
o=((\ x. (\ y. y)) (\ a. a)) d=(\ y. y)
o=(((\ x. (\ y. y)) (\ a. a)) (\ b. b)) d=(\ b. b)
o=((\ x. (x x)) (\ x. (x x))) d=Error
o=(((\ x. (\ y. x)) (\ a. a)) ((\ x. (x x)) (\ x. (x x)))) d=(\ a. a)
o=((\ a. (\ b. (a (a (a b))))) (\ c. (\ d. (c (c d))))) d=(\ b. (\ d. (b (b (b (b (b (b (b (b d))))))))))
o=((\ f. (\ x. (f x))) (\ y. (\ x. y))) d=(\ x. (\ x. x))
*/

这是半荒谬的:

#define R return
#define E if(i>=M||j>=M)R-1;
#define H d[j++]
enum{O=40,C,M=3999}; // assume ascii
signed char Q[M],D[M],t[M],Z,*o=Q,*d=D,*T;
int m,n,s,c,w;

K(i,j,k)
{!Z&&(Z=t[O]=1)+(t[C]=-1); //inizializza tabelle

 E;if(!o[i]){H=0;R 0;}
 if((c=t[o[i]]+t[o[i+1]])!=2||o[i+2]!=92)
      {H=o[i++]; R K(i,j,i);}
 for(i+=2,w=0;i<M&&o[i]&&c;++i)
         c+=t[o[i]],!w&&c==1?w=i:0;
 E;
 if(c){for(;H=o[i++];)E;R 0;} 
//  01234567w12 i
//  ((/ x. x) z)
//   x                 w              z
// o[k+4]..o[k+5];  o[k+7]..o[w];  o[w+2]..o[i-1]

// sostituzione
// sostituisce a x z in w e lo scrive in d
for(c=k+7,n=j;c<w&&j<M;++c)
      if(o[c]==o[k+4])
         {if(o[c+1]==46) // non puo' sostituire una variabile dove c'e' lambda
             {d[n++]=o[k++]; R K(k,n,k);}
          for(m=w+2;m<i-1&&j<M;++m)
                H=o[m];
         }
      else H=o[c];
 E;
 Z=2;
 R K(i,j,i);
}

char*L(char*a)
{for(s=n=0;n<M&&(o[n]=a[n]);++n);
 if(n==M)R 0;
 for(;++s<M;)
   {Z=0;
    n=K(0,0,0);
//    if(Z==2)printf("n=%d>%s\n", n, d);
    if(Z==2&&n!=-1)T=d,d=o,o=T;
    else break;
   }
 R n==-1||s>=M?0:d; 
}

规范要求\或λ,而不是/。它还需要支持多字母变量名。另外(我知道您知道这一点,但是是的,它仍然是错误的),这错误地将((/ f。(/ x。(fx)))(/ y。(/ x。y)))评估为( / x。(/ x。x))。
安德斯·卡塞格

我将/更改为\,存在不允许多字符变量的问题。如果测试其他,这也适用于其他解决方案
RosLuP
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.