我最近开始用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,...相反。
尽量减少自由变量的使用。