如何在具有动态作用域的语言中设计函数



我最近开始用Logo编写非平凡的程序(非平凡的,没有图形(。我遇到的主要障碍之一是动态范围界定。例如,考虑以下程序:

to foldl :f :acc :list [:index 1]
    output ifelse empty? :list [:acc] [
        (foldl :f (invoke :f :acc first :list :index) butfirst :list :index + 1)
    ]
end
to permute :list
    output ifelse empty? :list [[[]]] [
        foldl [[permutations item index]
            sentence :permutations map [[list]
                fput :item :list
            ] permute delete :index :list
        ] [] :list
    ]
end

permute 函数适用于它为其生成输出[[]]的空列表[]以及包含单个项目 [a ] 的列表,它为其生成输出[[a]] 。但是,对于具有两个或多个元素的列表,它会失败。

猜为什么会失败?从permute传递给foldl的 lambda 函数访问自由变量list并且由于foldl也有一个名为list的局部变量,因此它访问了错误的变量。由于foldl是以递归方式定义的,因此list变量在每次迭代时都会不断缩小。

我通过在 foldl 函数中保存原始列表的副本来解决此问题,如下所示:

to foldl :f :acc :list [:index 1] [:original :list]
    output ifelse empty? :list [:acc] [
        (foldl :f (invoke :f :acc first :list :index :original)
            butfirst :list :index + 1 :original)
    ]
end
to permute :list
    output ifelse empty? :list [[[]]] [
        foldl [[permutations item index list]
            sentence :permutations map [[list]
                fput :item :list
            ] permute delete :index :list
        ] [] :list
    ]
end

然而,我花了晚上的大部分时间才弄清楚是什么导致了这个奇怪的错误。我以前从未使用过具有动态作用域的语言进行编程(保存 bash 脚本的小片段(。

因此,我的问题如下:在用具有动态作用域的语言编写函数时,您应该记住什么?最佳实践是什么?如何避免常见的陷阱?

请记住

,这些语言没有闭包。

也许他们有,但有一个额外的结构(就像几十年前的一些Lisp语言(。更糟糕的是,有时解释器和编译器有不同的语义 - 就像几十年前的一些旧Lisp方言一样。

Lisp 主要转向词汇绑定是有原因的(Scheme 在 70 年代中期探索了它,Common Lisp 在 80 年代中期得到了它,Emacs Lisp 最近才得到了对它的支持(。

基本上,如果你想做高级函数式编程,远离动态范围的语言。

使用SML,Scheme,CL,Haskell,Racket,OCAML,...相反。

尽量减少自由变量的使用。

最新更新