lisphy

Dynamic bindings with let in Hy?


Comming from Common Lisp, I'm trying to use let to shadow the value of a global variable dynamically.

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (let [glob x]
    (callee))
  (callee))

(nonl 39)

=>
18
18
18

Is there a way to make this work so that the second call to callee gives 39?

[EDIT]

Based on gilch's response i wrote the following draft using contextvars:

(import contextvars :as cv)

(defmacro defparam [symbol value]
  (let [command (. f"{symbol} = cv.ContextVar('{symbol}')")]
    `(do (exec ~command)
         (.set ~symbol ~value))))

(defmacro parameterize [symbol value #* body]
  `(. (cv.copy-context)
      (run (fn [] (do (.set ~symbol ~value)
                      ~@body)))))

(defn callee []
  (glob.get))

;;;;;;;;;

(defparam glob 18)

(callee)                  => 18
(parameterize glob 39
  (callee))               => 39
(callee)                  => 18

Thanks for the answers!


Solution

  • There's no built-in way to give a global variable a dynamically scoped temporary value in Python (and Hy doesn't add a macro for it or anything), so you have to do it yourself:

    (setv glob 18)
    
    (defn callee []
      (print glob))
    
    (defn nonl [x]
      (callee)
      (global glob)
      (setv old-glob glob)
      (try
        (setv glob x)
        (callee)
        (finally
          (setv glob old-glob)))
      (callee))
    
    (nonl 39)
    

    A macro for this might look like:

    (defmacro dyn-rebind-global [symbol new-value #* body]
      (setv old-value (hy.gensym))
      `(do
        (global ~symbol)
        (setv ~old-value ~symbol)
        (try
          (setv ~symbol ~new-value)
          ~@body
          (finally
            (setv ~symbol ~old-value)))))
    

    Then you could use it like this:

    (setv glob 18)
    
    (defn callee []
      (print glob))
    
    (defn nonl [x]
      (callee)
      (dyn-rebind-global glob x
        (callee))
      (callee))
    
    (nonl 39)