2012-10-18 18 views
7

Jestem zdezorientowany, w jaki sposób def i niech różne zmienne wiązania. Czy ktoś może mi wyjaśnić, dlaczego to działa:Rekursja wewnątrz funkcji let

(def leven 
    (memoize (fn [x y] 
    (cond (empty? x) (count y) 
     (empty? y) (count x) 
     :else (min (+ (leven (rest x) y) 1) 
        (+ (leven x (rest y)) 1) 
        (+ (leven (rest x) (rest y)) (if (= (first x) (first y)) 0 1)) 
      ) 
))) 
) 

Ale gdy próbuję zadeklarować funkcję niech nie kompilacji:

(def leven 
    (let [l (memoize (fn [x y] 
    (cond (empty? x) (count y) 
      (empty? y) (count x) 
      :else (min (+ (l (rest x) y) 1) 
         (+ (l x (rest y)) 1) 
         (+ (l (rest x) (rest y)) (if (= (first x) (first y)) 0 1)) 
       ) 
    ) 
    ))] 
    (l x y) 
    ) 
) 

EDIT: To działa, stosując technikę pokazał przez Ankur.

(defn leven [x y] 
(let [l (memoize (fn [f x y] 
(cond (empty? x) (count y) 
     (empty? y) (count x) 
     :else (min (+ (f f (rest x) y) 1) 
        (+ (f f x (rest y)) 1) 
        (+ (f f (rest x) (rest y)) (if (= (first x) (first y)) 0 1)) 
      ) 
) 
)) 
magic (partial l l)] 
(magic x y) 
) 
) 

Odpowiedz

7

Poniżej znajduje się przykład wykonania tego, o co prosiłeś. Używam silni tylko przez wzgląd na prostotę i dodał println w silni, aby upewnić się, że memoization działa poprawnie

(let [fact (memoize (fn [f x] 
         (println (str "Called for " x)) 
         (if (<= x 1) 1 (* x (f f (- x 1)))))) 
     magic (partial fact fact)] 
    (magic 10) 
    (magic 11)) 

Najpierw obliczyć silnię 10, a potem 11, w którym to przypadku nie powinna ponownie zadzwonić silnia do 10 till 1 jak to zostało zapamiętane.

Called for 10 
Called for 9 
Called for 8 
Called for 7 
Called for 6 
Called for 5 
Called for 4 
Called for 3 
Called for 2 
Called for 1 
Called for 11 
39916800 
+0

Bardzo interesujące. Więc po prostu przekazujesz funkcję jako argument, więc kompilator nie myli się, ponieważ nie jest zdefiniowany. Nie mogę tego teraz wypróbować, ale spróbuję tego później. – onit

6

Formularz let wiąże nazwy kolejno więc w drugim definicji funkcji nazwa l nie istnieje podczas próby odnoszą się do niego. Można użyć letfn (z niewielkimi modów) lub podać określoną funkcję nazwę i zamiast odnosić się do tego, a nie, jak tak:

(def leven 
    (let [l (memoize (fn SOME-NAME [x y] 
    (cond 
     (empty? x) (count y) 
     (empty? y) (count x) 
     :else (min (+ (SOME-NAME (rest x) y) 1) 
       (+ (SOME-NAME x (rest y)) 1) 
       (+ (SOME-NAME (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))] 
l)) 

Jak można zauważyć zmienić powrocie z let być l się ponieważ to jest to, co chcesz leven związane. The (l x y) był problematyczny, ponieważ odwoływał się do wiązań tylko lokalnych dla funkcji i nie był dostępny dla let.

+2

Czy funkcja SOME-NAME nie traci korzyści z memoize, gdy jest używana w ten sposób? Nie musisz wywoływać funkcji memoize zwraca, lub nie jest możliwe, aby funkcja rekursywna pamiętana w instrukcji let? – onit

+1

@onit Definicja 'leven' może zostać zmieniona w celu uzyskania korzyści z zapamiętywania, przenosząc' SOME-NAME' jako pierwszy argument: '(fn [SOME-NAME xy]', a następnie również zamieniając połączenia na 'SOME -NAME' na '(SOME-NAME SOME-NAME ...)' i na końcu zastępując zwracaną wartość 'l' jako' (częściowe ll) '. –