Каков стандартный способ записи вложенных определений (как в схеме) для clojure? - PullRequest
11 голосов
/ 27 марта 2012

Все примеры взяты из Книги SICP: http://sicpinclojure.com/?q=sicp/1-3-3-procedures-general-methods

Это было мотивировано из серии видеороликов MIT на LISP - http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/2a-higher-order-procedures/

В схеме вы можете поместить 'define' внутри другого 'define':

(define (close-enough? v1 v2)
    (define tolerance 0.00001)
    (< (abs (- v1 v2)) tolerance ) )

В clojure есть оператор let, с той лишь разницей, что он вложен:

(defn close-enough? [v1 v2]
    (let [tolerance 0.00001]
         (< (Math/abs (- v1 v2) ) 
            tolerance) ) )

Но как насчет переписать в clojure нечто большее, как это?:

(define (sqrt x)
  (define (fixed-point f first-guess)
    (define (close-enough? v1 v2)
      (define tolerance 0.00001)
      (< (abs (- v1 v2)) tolerance))
    (define (try guess)
      (let ((next (f guess)))
         (if (close-enough? guess next)
            next
            (try next))))
    (try first-guess))
  (fixed-point (lambda (y) (average y (/ x y)))
           1.0))

Это на самом деле работает, но выглядит очень нетрадиционно ...

(defn sqrt [n]
  (let [precision       10e-6
        abs             #(if (< % 0) (- %) %)
        close-enough?   #(-> (- %1 %2) abs (< precision))
        averaged-func   #(/ (+ (/ n %) %) 2)
        fixed-point     (fn [f start]
                            (loop [old start
                                   new (f start)]
                                (if (close-enough? old new) 
                                    new
                                    (recur new (f new) ) ) ) )]

        (fixed-point averaged-func 1) ) )

 (sqrt 10)

ОБНОВЛЕНО март / 8/2012

Спасибо за ответ!

По сути, 'letfn' не слишком отличается от 'let' - вызываемые функции должны быть вложены в определение 'letfn' (в отличие от Scheme, где функции используются в следующем sexp после его определений и только существуют в рамках функции верхнего уровня, в которой она определена).

Итак, еще один вопрос ... Почему clojure не дает возможности делать то, что делает схема? Это какое-то решение по дизайну языка? Что мне нравится в схеме организации:

  • 1) Инкапсуляция идей, так что я, как программист, имею представление о том, какие маленькие блоки используются с большим блоком - особенно если я использую маленькие блоки только один раз в большом блоке (по любой причине , даже если маленькие блоки полезны сами по себе).

  • 2) Это также останавливает загрязнение пространства имен небольшими процедурами, которые бесполезны для конечного пользователя (я написал программы clojure, вернулся к ним через неделю и мне пришлось заново изучать мой код, потому что он был в плоской структуре, и я чувствовал, что смотрю на код наизнанку, а не сверху вниз).

  • 3) Общий интерфейс определения метода, так что я могу вытащить определенный подметод, отменить его тестирование и вставить измененную версию обратно без особых проблем.

Почему это не реализовано в clojure?

Ответы [ 3 ]

16 голосов
/ 27 марта 2012

Стандартный способ написания вложенных именованных процедур в clojure заключается в использовании letfn.

в качестве отдельного примера. Ваш пример использования вложенных функций довольно подозрительно.все функции в этом примере могут быть нелокальными функциями верхнего уровня, поскольку они более или менее полезны сами по себе и не закрываются ни над чем, кроме друг друга.

13 голосов
/ 22 июня 2012

Люди, критикующие его размещение этого во всех одной функции, не понимают контекст, почему это было сделано таким образом. SICP, откуда и взят пример, пытался проиллюстрировать концепцию модуля, но без добавления каких-либо других конструкций в базовый язык. Таким образом, «sqrt» - это модуль с одной функцией в интерфейсе, а остальные являются локальными или частными функциями в этом модуле. Я полагаю, что это было основано на схеме R5RS, и более поздние схемы добавили стандартную модульную конструкцию, я думаю (?). Но, тем не менее, это больше демонстрирует принцип сокрытия реализации.

Опытный планировщик также просматривает аналогичные примеры вложенных локальных функций, но обычно и для скрытия реализации, и для закрытия значений.

Но даже если это не педагогический пример, вы можете увидеть, как это очень легкий модуль, и я, вероятно, написал бы его таким образом в более крупном "реальном" модуле. Повторное использование в порядке, если это запланировано. В противном случае вы просто выставляете функции, которые, вероятно, не идеально подойдут для того, что вам нужно позже, и в то же время обременяете эти функции непредвиденными сценариями использования, которые впоследствии могут их сломать.

6 голосов
/ 24 января 2014

letfn - это стандартный способ.

Но поскольку Clojure - это Лисп, вы можете создать (почти) любую семантику, какую захотите. Вот подтверждение концепции, которая определяет define в терминах letfn.

(defmacro define [& form]
  (letfn [(define? [exp]
            (and (list? exp) (= (first exp) 'define)))
          (transform-define [[_ name args & exps]]
            `(~name ~args
               (letfn [~@(map transform-define (filter define? exps))]
                 ~@(filter #(not (define? %)) exps))))]
    `(defn ~@(transform-define `(define ~@form)))))

(define sqrt [x]
  (define average [a b] (/ (+ a b) 2))
  (define fixed-point [f first-guess]
    (define close-enough? [v1 v2]
      (let [tolerance 0.00001]
        (< (Math/abs (- v1 v2)) tolerance)))
    (define tryy [guess]
      (let [next (f guess)]
        (if (close-enough? guess next)
          next
          (tryy next))))
    (tryy first-guess))
  (fixed-point (fn [y] (average y (/ x y)))
               1.0))

(sqrt 10)   ; => 3.162277660168379

Для реального кода вы бы хотели изменить define так, чтобы он вел себя больше как R5RS: разрешить значения не-fn, доступны в defn, defmacro, let, letfn и fn и убедитесь, что внутренние определения находятся в начале включающего тела.

Примечание: мне пришлось переименовать try в tryy. По-видимому, try - это специальная нефункциональная, не-макроструктура, для которой переопределение молча завершается неудачей.

...