Есть ли возможность использовать def внутри функции в clojure для рекурсивного определения? - PullRequest
0 голосов
/ 02 июня 2019

Я пытаюсь создать функцию sqrt, используя потоки в Clojure.Для этого мне нужно определить поток внутри функции и вернуть его.Проблема заключается в том, что поток определяется в терминах самого себя;следовательно, использование let невозможно, а использование def является фиктивным, поскольку оно влияет на глобальную область видимости.Есть ли способ симулировать использование def внутри функции, которая не влияет на глобальную область видимости?

(defmacro cons-stream [a b]
  (list 'lazy-seq (list 'cons a (list 'lazy-seq b))))

(defn stream-car [stream] (first stream))

(defn stream-cdr [stream] (rest stream))

(defn stream-map [proc & streams]
  (if (empty? (first streams))
    '()
    (cons-stream (apply proc (map stream-car streams))
                 (apply stream-map proc (map stream-cdr streams)))))

(defn average [a b] (/ (+ a b) 2.0))

(defn sqrt-improve [guess x]
  (average guess (/ x guess)))

(defn sqrt-stream [x]
  (def guesses (cons-stream 1.0
                            (stream-map #(sqrt-improve % x) guesses)))
  guesses)

Я не хочу, чтобы sqrt-stream создавал глобальный поток догадок.

EDIT

В clojure определение let доступно только после его оценки.,Так что это определение выдает ошибку.

(defn sqrt-stream [x]
  (let [guesses (cons-stream 1.0
                             (stream-map #(sqrt-improve % x) guesses))]
    guesses))

Ответы [ 2 ]

2 голосов
/ 02 июня 2019

@ Чарльз Даффи прав. A promise можно использовать здесь:

(defn sqrt-stream [x]
  (let [p (promise)]

    (deliver p (cons-stream 1.0
                            (stream-map #(sqrt-improve % x) @p)))
    @p))

Это, похоже, проблема XY. Просто используйте существующие конструкции (как показано ниже в моем предыдущем ответе). Также обратите внимание, что stream-map - это просто встроенный map, и вы можете использовать синтаксическую кавычку (`) для улучшения cons-stream:

(defmacro cons-stream [a b]
  `(lazy-seq (cons ~a (lazy-seq ~b)))) ; ~ unquotes a and b

(defn average [a b]
  (/ (+ a b) 2.0))

(defn sqrt-improve [guess x]
  (average guess (/ x guess)))

(defn sqrt-stream [x]
  (let [p (promise)]

    (deliver p (cons-stream 1.0
                            (map #(sqrt-improve % x) @p)))
    @p))

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

(defn average [a b]
  (/ (+ a b) 2.0))

(defn sqrt-improve [guess x]
  (average guess (/ x guess)))

(defn sqrt-stream [n]
  (iterate #(sqrt-improve % n) ; Apply this function over and over again
           1.0)) ; The initial value to iterate over

Затем используйте это как:

(->> (sqrt-stream 10)
     (take 10))

=>
(1.0
 5.5
 3.659090909090909
 3.196005081874647
 3.16245562280389
 3.162277665175675
 3.162277660168379
 3.162277660168379
 3.162277660168379
 3.162277660168379)

Вы можете получить окончательный ответ, получив столько результатов, сколько хотите, чтобы достичь желаемой точности, а затем получить окончательное (last) значение:

(->> (sqrt-stream 10)
     (take 100)
     (last))

=> 3.162277660168379
1 голос
/ 03 июня 2019

Я только что заметил, что @Carciginate добавил решение, основанное на iterate, к принятому ответу примерно за двенадцать часов до того, как я опубликовал следующий.


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

Ваши определения

(defn stream-car [stream] (first stream))
(defn stream-cdr [stream] (rest stream))

... может быть проще выражено как

(def stream-car first)
(def stream-cdr rest)

Другими словами,

  • stream-car является синонимом first
  • stream-cdr является синонимом rest.

Точно так же, ваш cons-stream по сути копирует старый lazy-cons, который теперь устарел.

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

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

(defn iterate [f x]
  (lazy-seq (cons x (iterate f (f x)))))

Затем мы можем определить ваш sqrt-stream как

(defn sqrt-stream [x]
  (iterate
    (fn [guess] (sqrt-improve guess x))
    1.0))

Например,

=> (take 10 (sqrt-stream 10))
(1.0 5.5 3.659090909090909 3.196005081874647 3.16245562280389 
 3.162277665175675 3.162277660168379 3.162277660168379 
 3.162277660168379 3.162277660168379)

Есть проблемы, которые растягивают репертуар последовательности Clojure, но это не одна из них.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...