Clojure binding of dynamic var not working as expected

匿名 (未验证) 提交于 2019-12-03 02:31:01

问题:

From what I understand, setting a new binding on a dynamic var affects all functions called within that binding, and all functions called from those functions.

Why does the binding appear to be lost in the first example below?

(def ^:dynamic *out-dir* "/home/user")  (binding [*out-dir* "/home/dave"] (map #(str *out-dir* %) [1 2 3])) ; gives:    ("/home/user1" "/home/user2" "/home/user3") ; expected: ("/home/dave1" "/home/dave2" "/home/dave3")  (binding [*out-dir* "/home/dave"] (conj (map #(str *out-dir* %) [1 2 3]) *out-dir*)) ; gives: ("/home/dave" "/home/dave1" "/home/dave2" "/home/dave3") 

回答1:

This is caused by lazyness - map returns a lazy sequence which is defined inside the binding but is evaluated outside. You need to force the evaluation from inside:

(binding [*out-dir* "/home/dave"]    (doall (map #(str *out-dir* %) [1 2 3]))) 


回答2:

It's true that laziness and dynamic bindings can cause problems; however, abandoning laziness is not the only solution. If you wish to preserve laziness (or to use dynamic bindings with pmap), use bound-fn or bound-fn*.

(def ^:dynamic x 0)  => (binding [x 3] (map #(+ x %) (range 10))) ;; (0 1 2 3 4 5 6 7 8 9)  => (binding [x 3] (map (bound-fn [y] (+ x y)) (range 10))) ;; (3 4 5 6 7 8 9 10 11 12)  => (binding [x 3] (map (bound-fn* #(+ % x)) (range 10))) ;; (3 4 5 6 7 8 9 10 11 12) 


回答3:

Another solution is to use Python-style generator functions available via lazy-gen and yield from the Tupelo library:

(ns tst.demo.core   (:use demo.core tupelo.test)   (:require     [tupelo.core :as t] )) (t/refer-tupelo)  (def ^:dynamic foo 1)  (dotest   (let [result (binding [foo 3]                  (lazy-gen                    (doseq [x (range 3)]                      (yield {:foo foo :x x})))) ]     (println result)))  result => ({:foo 3, :x 0}             {:foo 3, :x 1}            {:foo 3, :x 2}) 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!