Как проектировать функции в языках, которые имеют динамическую область видимости?
Недавно я начал писать нетривиальные программы в 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]]
, Однако это не работает для списков с двумя или более элементами.
Угадайте, почему это не удается? Лямбда-функция передана foldl
от permute
получает доступ к свободной переменной 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).
Поэтому мой вопрос заключается в следующем: что вы должны иметь в виду при написании функций на языках с динамической областью видимости? Каковы лучшие практики? Как избежать распространенных ошибок?
2 ответа
Я хотел бы иметь в виду, что эти языки не имеют замыканий.
Возможно, они есть, но с дополнительной конструкцией (как некоторые языки Lisp несколько десятилетий назад). Хуже того, иногда Интерпретатор и Компилятор имели разную семантику - как в некоторых старых диалектах Лиспа десятилетия назад.
Есть причина, по которой Лисп в основном переключился на лексическое связывание (Схема исследовала его в середине 70-х годов, Common Lisp получил его в середине 80-х, Emacs Lisp только недавно получил его поддержку).
По сути, если вы хотите заниматься продвинутым функциональным программированием, держитесь подальше от динамически ограниченных языков.
Вместо этого используйте SML, Scheme, CL, Haskell, Racket, OCAML, ....