Как проектировать функции в языках, которые имеют динамическую область видимости?

Недавно я начал писать нетривиальные программы в 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, ....

Другие вопросы по тегам