A Shared Data Model for Host Overrides - blancas/eisen GitHub Wiki

This page describes a more flexible and robust way for user code to modify a host program. It's designed to support a data model shared by the host program and user code without imposing any particular design or architectural requirements.

The main features of this mechanism are as follows:

  • The host program stores named data and functions into a model.
  • Both the host and user code can fetch values from the model.
  • User code overrides values in the model, thus changing the host.
  • The model is created and maintained through the Eisen API.

The following sections describe the APIs and offer an example. To follow the illustrations we load the Eisen core namespace.

(use 'blancas.eisen.core)

host-model adds one or more key-value pairs to the data model (which is a regular map). Keys must be unquoted symbols. Values may be any Clojure object.

(host-model domestic 737 abroad 747)
;; {abroad 747, domestic 737}

fetch retrieves the value of the supplied symbol key. The key should not be quoted. If the key is not in the model it returns nil.

(fetch abroad)
;; 747
(fetch carrier)
;; nil

call takes a symbol, possibly followed by any number of arguments. It retrieves the function value using the key and calls the function using the rest of the arguments. It returns nil if the key is not in the model.

(host-model avg (fn [x y] (quot (+ x y) 2)))
;; {avg #<...>, abroad 747, domestic 737}
(call avg 3 5)
;; 4
(call foo)
;; nil

with-host-model is a macro that evaluates its body using thread binding for the data model. This macro should be used when the model needs to be visible to code in any thread other than the main thread.

(with-host-model (.start (Thread. (fn [] (println (fetch domestic))))))
;; 737

Access from Eisen Code

User code written in Eisen has read and write access to the host data model. Access to data and functions is done through a lexical convention: one or more underscores starting a name denote a host variable. We now switch to the Eisen repl for the examples.

(init-eisen)
(eisen-repl)
;; user:

The prompt is omitted to make it easy to copy and paste the code. Press Enter at the continuation prompt > to run the command.

_abroad
-- 747
_avg 100 50
-- 75

Overrides of host data and functions is done with setq and setv.

setq sets the value of a host data element.

setq domestic = 767
-- {domestic 767, avg #<...>}
_domestic
-- 767

setv sets the value of a host function. It takes the value of an Eisen function (i.e., its var).

fun average x y =  (x + y) / 2.0
-- #'user/average
average 50 100
-- 75.0

We can set the host avg function to our local one that returns a floating-point result.

setv avg = average
-- {domestic 767, avg #<...>}

Now using the host model.

_avg 50 100
-- 75.0

Another Hello World

We have another sample program, based on Hello World, to illustrate the host model approach. Again, the host program lets user code override the hook to change the greeting.

(use 'blancas.eisen.core)

;; To run from the REPL:
(comment
  (load-file "src/main/resources/hello/ciao.clj")
)

;; Expected location of user code.
(def eisen-file "src/main/resources/hello/ciao.esn")

;; The host model
(host-model
  greeting "Hello, %s!\n"  ;; Default form of the greeting.
  subject  "world"         ;; Default receiver of the greeting.
  hook     nil)            ;; Something to do before the greeting.

(defn greet
  "Greets someone or something."
  [g x] (printf g x))

;; Extensions.
(init-eisen)         ;; Setup the default Eisen configuration.
(eisenf eisen-file)  ;; Run user code from the well-known place.

;; Main program.

(call hook)                               ;; Call the user-defined function.
(greet (fetch greeting) (fetch subject))  ;; Greets the subject.

The following Eisen code will ask who's there to offer an appropriate greeting.

module ciao

(* Asks for the name on behalf of the host program. *)

fun ask = 
    let previous = _subject
    in
        print "Who are you? ";
        flush;
        setq subject = readLine;
        println "Ciao," previous
    end

(* Sets the hook as the function ask. *)

setv hook = ask

Function asks has access to the host model and can get the value of the initial subject, using the same symbol but with a leading underscore.

Now we can run the program and obtain the new functionality.

(load-file "src/main/resources/hello/ciao.clj")
Who are you? Armando
Ciao, world
Hello, Armando!
⚠️ **GitHub.com Fallback** ⚠️