Initial commit
This commit is contained in:
commit
2ad806a8fc
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.DS_Store
|
214
LICENSE
Normal file
214
LICENSE
Normal file
|
@ -0,0 +1,214 @@
|
|||
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
|
||||
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
|
||||
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
|
||||
|
||||
1. DEFINITIONS
|
||||
|
||||
"Contribution" means:
|
||||
|
||||
a) in the case of the initial Contributor, the initial code and
|
||||
documentation distributed under this Agreement, and
|
||||
|
||||
b) in the case of each subsequent Contributor:
|
||||
|
||||
i) changes to the Program, and
|
||||
|
||||
ii) additions to the Program;
|
||||
|
||||
where such changes and/or additions to the Program originate from and are
|
||||
distributed by that particular Contributor. A Contribution 'originates' from
|
||||
a Contributor if it was added to the Program by such Contributor itself or
|
||||
anyone acting on such Contributor's behalf. Contributions do not include
|
||||
additions to the Program which: (i) are separate modules of software
|
||||
distributed in conjunction with the Program under their own license
|
||||
agreement, and (ii) are not derivative works of the Program.
|
||||
|
||||
"Contributor" means any person or entity that distributes the Program.
|
||||
|
||||
"Licensed Patents" mean patent claims licensable by a Contributor which are
|
||||
necessarily infringed by the use or sale of its Contribution alone or when
|
||||
combined with the Program.
|
||||
|
||||
"Program" means the Contributions distributed in accordance with this
|
||||
Agreement.
|
||||
|
||||
"Recipient" means anyone who receives the Program under this Agreement,
|
||||
including all Contributors.
|
||||
|
||||
2. GRANT OF RIGHTS
|
||||
|
||||
a) Subject to the terms of this Agreement, each Contributor hereby grants
|
||||
Recipient a non-exclusive, worldwide, royalty-free copyright license to
|
||||
reproduce, prepare derivative works of, publicly display, publicly perform,
|
||||
distribute and sublicense the Contribution of such Contributor, if any, and
|
||||
such derivative works, in source code and object code form.
|
||||
|
||||
b) Subject to the terms of this Agreement, each Contributor hereby grants
|
||||
Recipient a non-exclusive, worldwide, royalty-free patent license under
|
||||
Licensed Patents to make, use, sell, offer to sell, import and otherwise
|
||||
transfer the Contribution of such Contributor, if any, in source code and
|
||||
object code form. This patent license shall apply to the combination of the
|
||||
Contribution and the Program if, at the time the Contribution is added by the
|
||||
Contributor, such addition of the Contribution causes such combination to be
|
||||
covered by the Licensed Patents. The patent license shall not apply to any
|
||||
other combinations which include the Contribution. No hardware per se is
|
||||
licensed hereunder.
|
||||
|
||||
c) Recipient understands that although each Contributor grants the licenses
|
||||
to its Contributions set forth herein, no assurances are provided by any
|
||||
Contributor that the Program does not infringe the patent or other
|
||||
intellectual property rights of any other entity. Each Contributor disclaims
|
||||
any liability to Recipient for claims brought by any other entity based on
|
||||
infringement of intellectual property rights or otherwise. As a condition to
|
||||
exercising the rights and licenses granted hereunder, each Recipient hereby
|
||||
assumes sole responsibility to secure any other intellectual property rights
|
||||
needed, if any. For example, if a third party patent license is required to
|
||||
allow Recipient to distribute the Program, it is Recipient's responsibility
|
||||
to acquire that license before distributing the Program.
|
||||
|
||||
d) Each Contributor represents that to its knowledge it has sufficient
|
||||
copyright rights in its Contribution, if any, to grant the copyright license
|
||||
set forth in this Agreement.
|
||||
|
||||
3. REQUIREMENTS
|
||||
|
||||
A Contributor may choose to distribute the Program in object code form under
|
||||
its own license agreement, provided that:
|
||||
|
||||
a) it complies with the terms and conditions of this Agreement; and
|
||||
|
||||
b) its license agreement:
|
||||
|
||||
i) effectively disclaims on behalf of all Contributors all warranties and
|
||||
conditions, express and implied, including warranties or conditions of title
|
||||
and non-infringement, and implied warranties or conditions of merchantability
|
||||
and fitness for a particular purpose;
|
||||
|
||||
ii) effectively excludes on behalf of all Contributors all liability for
|
||||
damages, including direct, indirect, special, incidental and consequential
|
||||
damages, such as lost profits;
|
||||
|
||||
iii) states that any provisions which differ from this Agreement are offered
|
||||
by that Contributor alone and not by any other party; and
|
||||
|
||||
iv) states that source code for the Program is available from such
|
||||
Contributor, and informs licensees how to obtain it in a reasonable manner on
|
||||
or through a medium customarily used for software exchange.
|
||||
|
||||
When the Program is made available in source code form:
|
||||
|
||||
a) it must be made available under this Agreement; and
|
||||
|
||||
b) a copy of this Agreement must be included with each copy of the Program.
|
||||
|
||||
Contributors may not remove or alter any copyright notices contained within
|
||||
the Program.
|
||||
|
||||
Each Contributor must identify itself as the originator of its Contribution,
|
||||
if any, in a manner that reasonably allows subsequent Recipients to identify
|
||||
the originator of the Contribution.
|
||||
|
||||
4. COMMERCIAL DISTRIBUTION
|
||||
|
||||
Commercial distributors of software may accept certain responsibilities with
|
||||
respect to end users, business partners and the like. While this license is
|
||||
intended to facilitate the commercial use of the Program, the Contributor who
|
||||
includes the Program in a commercial product offering should do so in a
|
||||
manner which does not create potential liability for other Contributors.
|
||||
Therefore, if a Contributor includes the Program in a commercial product
|
||||
offering, such Contributor ("Commercial Contributor") hereby agrees to defend
|
||||
and indemnify every other Contributor ("Indemnified Contributor") against any
|
||||
losses, damages and costs (collectively "Losses") arising from claims,
|
||||
lawsuits and other legal actions brought by a third party against the
|
||||
Indemnified Contributor to the extent caused by the acts or omissions of such
|
||||
Commercial Contributor in connection with its distribution of the Program in
|
||||
a commercial product offering. The obligations in this section do not apply
|
||||
to any claims or Losses relating to any actual or alleged intellectual
|
||||
property infringement. In order to qualify, an Indemnified Contributor must:
|
||||
a) promptly notify the Commercial Contributor in writing of such claim, and
|
||||
b) allow the Commercial Contributor tocontrol, and cooperate with the
|
||||
Commercial Contributor in, the defense and any related settlement
|
||||
negotiations. The Indemnified Contributor may participate in any such claim
|
||||
at its own expense.
|
||||
|
||||
For example, a Contributor might include the Program in a commercial product
|
||||
offering, Product X. That Contributor is then a Commercial Contributor. If
|
||||
that Commercial Contributor then makes performance claims, or offers
|
||||
warranties related to Product X, those performance claims and warranties are
|
||||
such Commercial Contributor's responsibility alone. Under this section, the
|
||||
Commercial Contributor would have to defend claims against the other
|
||||
Contributors related to those performance claims and warranties, and if a
|
||||
court requires any other Contributor to pay any damages as a result, the
|
||||
Commercial Contributor must pay those damages.
|
||||
|
||||
5. NO WARRANTY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
|
||||
AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
|
||||
EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
|
||||
CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
|
||||
PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
|
||||
appropriateness of using and distributing the Program and assumes all risks
|
||||
associated with its exercise of rights under this Agreement , including but
|
||||
not limited to the risks and costs of program errors, compliance with
|
||||
applicable laws, damage to or loss of data, programs or equipment, and
|
||||
unavailability or interruption of operations.
|
||||
|
||||
6. DISCLAIMER OF LIABILITY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
|
||||
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
|
||||
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
|
||||
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGES.
|
||||
|
||||
7. GENERAL
|
||||
|
||||
If any provision of this Agreement is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of the
|
||||
remainder of the terms of this Agreement, and without further action by the
|
||||
parties hereto, such provision shall be reformed to the minimum extent
|
||||
necessary to make such provision valid and enforceable.
|
||||
|
||||
If Recipient institutes patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
|
||||
(excluding combinations of the Program with other software or hardware)
|
||||
infringes such Recipient's patent(s), then such Recipient's rights granted
|
||||
under Section 2(b) shall terminate as of the date such litigation is filed.
|
||||
|
||||
All Recipient's rights under this Agreement shall terminate if it fails to
|
||||
comply with any of the material terms or conditions of this Agreement and
|
||||
does not cure such failure in a reasonable period of time after becoming
|
||||
aware of such noncompliance. If all Recipient's rights under this Agreement
|
||||
terminate, Recipient agrees to cease use and distribution of the Program as
|
||||
soon as reasonably practicable. However, Recipient's obligations under this
|
||||
Agreement and any licenses granted by Recipient relating to the Program shall
|
||||
continue and survive.
|
||||
|
||||
Everyone is permitted to copy and distribute copies of this Agreement, but in
|
||||
order to avoid inconsistency the Agreement is copyrighted and may only be
|
||||
modified in the following manner. The Agreement Steward reserves the right to
|
||||
publish new versions (including revisions) of this Agreement from time to
|
||||
time. No one other than the Agreement Steward has the right to modify this
|
||||
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
|
||||
Eclipse Foundation may assign the responsibility to serve as the Agreement
|
||||
Steward to a suitable separate entity. Each new version of the Agreement will
|
||||
be given a distinguishing version number. The Program (including
|
||||
Contributions) may always be distributed subject to the version of the
|
||||
Agreement under which it was received. In addition, after a new version of
|
||||
the Agreement is published, Contributor may elect to distribute the Program
|
||||
(including its Contributions) under the new version. Except as expressly
|
||||
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
|
||||
licenses to the intellectual property of any Contributor under this
|
||||
Agreement, whether expressly, by implication, estoppel or otherwise. All
|
||||
rights in the Program not expressly granted under this Agreement are
|
||||
reserved.
|
||||
|
||||
This Agreement is governed by the laws of the State of New York and the
|
||||
intellectual property laws of the United States of America. No party to this
|
||||
Agreement will bring a legal action under this Agreement more than one year
|
||||
after the cause of action arose. Each party waives its rights to a jury trial
|
||||
in any resulting litigation.
|
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# exav - EXecution As a Value
|
||||
|
||||
## Why?
|
||||
|
||||
The fundamental building block of Clojure is the immutable value. Values can be shared, values can be saved, values can be inspected, values can be transformed.
|
||||
|
||||
Programs often must ask for input from the external world. exav models this much like core.async; we write code which looks very much like a simple function which is producing a value, except that sprinkled throughout are operations which cause execution to be suspended until some external event takes place to supply us with a new value.
|
||||
|
||||
core.async is wonderful, but its fundamental building block is the channel, which is not a value. We have no way of looking inside the computation which is suspended. We have no way of rewinding it. We have no way of persisting it. core.async code builds up a black box, to be fed with data.
|
||||
|
||||
I want the ability to rewind time, hotload new code, then run time forward again. I want the ability to script complex interactions in a game that take place over arbitrary amounts of time, and for my entire game state to exist in a map that can be saved as EDN or Transit. exav gives me the tools to do this.
|
||||
|
||||
## Usage
|
||||
|
||||
exav currently has one basic building block: the `proc` macro. The `proc` macro works very much like the `go` macro in core.async, except that instead of returning a channel, it returns a function, and instead of blocking on `>!` or `<!`, it blocks on calls to `wait`. The function returned by proc takes zero, one, or two arguments.
|
||||
|
||||
(let [p (proc (str "Hello, " (wait :name)))
|
||||
; calling a proc with no arguments starts the process
|
||||
state (p)
|
||||
|
||||
; a proc will always return a map with a standard structure
|
||||
; if the :waitfor key exists, the value is whatever was passed to (wait)
|
||||
waitfor (:waitfor state) ; => :name
|
||||
|
||||
; calling a proc with two arguments continues the process
|
||||
; you must pass the state returned by the proc, and the value to be returned by (wait)
|
||||
state2 (p state "Bob")
|
||||
|
||||
; if the :result key exists, it will be the only thing in the map, and is equal to the
|
||||
; final return value of the proc
|
||||
result (:result state2) ; => "Hello, Bob"
|
||||
|
||||
; note that it is completely legal to hold onto and re-use old states!
|
||||
result2 (:result (p state "Phil")) ; => "Hello, Phil"
|
||||
|
||||
; a proc can also be called with just one argument, which is the same as passing nil
|
||||
; for the second argument
|
||||
result3 (:result (p state)) ; => "Hello, "
|
||||
])
|
||||
|
||||
`wait` always takes a single argument; a value that describes what the proc is waiting for.
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2014 Jeremy Penner
|
||||
|
||||
Distributed under the Eclipse Public License either version 1.0 or (at
|
||||
your option) any later version.
|
3
doc/intro.md
Normal file
3
doc/intro.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Introduction to exav
|
||||
|
||||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
7
project.clj
Normal file
7
project.clj
Normal file
|
@ -0,0 +1,7 @@
|
|||
(defproject exav "0.1.0-SNAPSHOT"
|
||||
:description "EXecution As a Value, flexible tools for async"
|
||||
:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||
[org.clojure/core.async "0.1.346.0-17112a-alpha"]])
|
78
src/clj/exav/core.clj
Normal file
78
src/clj/exav/core.clj
Normal file
|
@ -0,0 +1,78 @@
|
|||
(ns exav.core
|
||||
(:require [clojure.core.async.impl.ioc-macros :as ioc]))
|
||||
|
||||
;; Goal:
|
||||
;; Convert an arbitrary clojure form that contains "blocking" calls, to a function which
|
||||
;; accepts a state and an argument (the result of the blocking call) and returns the next
|
||||
;; state and a "blocking" value. This state can then be persisted, arbitrarily rolled back,
|
||||
;; and re-run; it is a simple clojure value. No hidden local state is generated.
|
||||
|
||||
; "machine" is a mutable array of objects used by the core.async ioc-macros to implement
|
||||
; the state machine. standard indices into the array are used to implement various fields
|
||||
; (what state is running, the value of the previous expression, etc)
|
||||
|
||||
; We extend this with two extra fields - WAITFOR-IDX, which stores the value passed to (wait),
|
||||
; and WAITFOR-FLAG-IDX, which is set to true when (wait) is called, so that we can tell the
|
||||
; difference between a final return and (wait nil).
|
||||
(def ^:const WAITFOR-IDX ioc/USER-START-IDX)
|
||||
(def ^:const WAITFOR-FLAG-IDX (+ ioc/USER-START-IDX 1))
|
||||
(def ^:const LOCALS-START-IDX (+ ioc/USER-START-IDX 2))
|
||||
|
||||
(defn wait [ev]
|
||||
(assert nil "wait must be used in an proc block"))
|
||||
|
||||
(defn wait* [machine nextblock ev]
|
||||
"The function called to implement our custom terminator (last statement in a 'block')
|
||||
when the state machine is running. Returns nil to signal that the state machine should
|
||||
suspend execution (the other option is :recur, which would immediately move on to the
|
||||
next block)"
|
||||
(ioc/aset-all! machine WAITFOR-IDX ev
|
||||
WAITFOR-FLAG-IDX true
|
||||
ioc/STATE-IDX nextblock)
|
||||
nil)
|
||||
|
||||
(defn config-machine [machine state value]
|
||||
"Takes apart a state map as returned from a proc, and sets up all the values in an empty
|
||||
machine array to allow it to continue. Returns the state's thread bindings, basically just
|
||||
to pass to run-machine, because they are the only thing we need to configure whether or not
|
||||
we have a state."
|
||||
(assert (and (integer? (:state state))
|
||||
(vector? (:locals state)))
|
||||
"state is not valid")
|
||||
|
||||
; copy locals
|
||||
(doseq [[i local] (map vector (iterate inc LOCALS-START-IDX) (:locals state))]
|
||||
(ioc/aset-object machine i local))
|
||||
|
||||
(ioc/aset-all! machine
|
||||
; copy next state
|
||||
ioc/STATE-IDX (:state state)
|
||||
; copy argument
|
||||
ioc/VALUE-IDX value)
|
||||
(:bindings state))
|
||||
|
||||
(defn run-machine [machine bindings]
|
||||
"Given a machine array and thread bindings, runs the machine until it finishes or blocks,
|
||||
and returns a new state map with the results."
|
||||
(let [bindings (or bindings (clojure.lang.Var/getThreadBindingFrame))]
|
||||
(ioc/aset-all! machine ioc/BINDINGS-IDX bindings)
|
||||
(ioc/run-state-machine machine)
|
||||
(if (ioc/aget-object machine WAITFOR-FLAG-IDX)
|
||||
; blocked
|
||||
{:bindings bindings
|
||||
:locals (vec (for [i (range LOCALS-START-IDX (.length machine))]
|
||||
(ioc/aget-object machine i)))
|
||||
:state (ioc/aget-object machine ioc/STATE-IDX)
|
||||
:waitfor (ioc/aget-object machine WAITFOR-IDX)}
|
||||
; complete
|
||||
{:result (ioc/aget-object machine ioc/VALUE-IDX)})))
|
||||
|
||||
(defmacro proc [& body]
|
||||
`(let [machine-fn# ~(ioc/state-machine `(do ~@body) 2 (keys &env) {`wait `wait*})]
|
||||
(fn proc#
|
||||
([] (run-machine (machine-fn#) nil))
|
||||
([state#] (proc# state# nil))
|
||||
([state# value#]
|
||||
(let [machine# (machine-fn#)
|
||||
bindings# (config-machine machine# state# value#)]
|
||||
(run-machine machine# bindings#))))))
|
120
src/clj/exav/perfunct.clj
Normal file
120
src/clj/exav/perfunct.clj
Normal file
|
@ -0,0 +1,120 @@
|
|||
(ns exav.perfunct)
|
||||
|
||||
; Persistable functions:
|
||||
; A function in clojure is a first-class value that can be passed around and composed.
|
||||
; It can't, however, be sensibly persisted, except in ad-hoc ways. In general, there
|
||||
; is not much of a use-case for persisting arbitrary functions. We don't really ever
|
||||
; want to be given an arbitrary block of clojure code, compile it, and run it - that's
|
||||
; a security disaster. It would often be nice, however, to have a first-class way of
|
||||
; _referring_ to a particular set of whitelisted functions, and be able to write down
|
||||
; and read those _references_.
|
||||
|
||||
; One could concievably just persist a clojure var, but that has its own set of problems.
|
||||
; Chief among them is that you would still require a whitelist to ensure that you're not
|
||||
; being tricked into calling "evil" functions in places that you didn't expect.
|
||||
; Another significant problem is that you lose any particular ability to use or persist
|
||||
; a closure.
|
||||
|
||||
; perfunct provides a simple solution to these problems. We introduce an object called
|
||||
; a peref (for "persistable reference"), which contains a simple value used to refer to
|
||||
; the function, can be directly called like a function (much like a var), and derefed (if
|
||||
; it is referring to a non-function unpersistable thing, such as an atom or channel).
|
||||
; We provide built-in hooks for pr-str and transit serialization, reader literals, and
|
||||
; functions for extracting a peref's value as clojure data.
|
||||
|
||||
; In addition to persisting references to functions, we can use this flexible
|
||||
; functionality to persist references to atoms, to core.async channels, to expensive
|
||||
; objects that should be instantiated lazily, or even to resources meant to be loaded
|
||||
; from disk.
|
||||
|
||||
; definvokable adapted from code by Meikel Brandmeyer
|
||||
; https://groups.google.com/forum/#!topic/clojure/pl4HgR9L_lg
|
||||
(def max-arities 20)
|
||||
|
||||
(defmacro definvokable
|
||||
[type fields f & deftype-tail]
|
||||
(let [args (repeatedly max-arities gensym)
|
||||
arity (fn [n]
|
||||
(let [args (take n args)]
|
||||
`(invoke [this# ~@args] (~f this# ~@args))))
|
||||
vararg `(invoke [this# ~@args more#]
|
||||
(apply ~f this# ~@args more#))
|
||||
apply-to `(applyTo [this# args#] (apply ~f this# args#))]
|
||||
`(deftype ~type
|
||||
~fields
|
||||
clojure.lang.IFn
|
||||
~@(map arity (range (inc max-arities)))
|
||||
~vararg
|
||||
~apply-to
|
||||
~@deftype-tail)))
|
||||
|
||||
(defn invoke-peref [peref & args] (apply @peref args))
|
||||
(definvokable Peref [lookup name params] invoke-peref
|
||||
clojure.lang.IDeref
|
||||
(deref [_] (apply (.lookup-fn lookup) name params)))
|
||||
|
||||
(defn invoke-peref-lookup [lookup name & args] (->Peref lookup name args))
|
||||
(definvokable PerefLookup [id lookup-fn] invoke-peref-lookup)
|
||||
|
||||
(defn peref-tag [p]
|
||||
(let [id (.id (.lookup p))]
|
||||
(str (namespace id) "/" (name id))))
|
||||
|
||||
(defn peref-data [p]
|
||||
(let [name (.name p)
|
||||
params (.params p)]
|
||||
(vec (cons name params))))
|
||||
|
||||
(defmethod print-method Peref [v ^java.io.Writer w]
|
||||
"Print an edn literal for a Peref"
|
||||
(let [tag (str "#" (peref-tag v) " ")]
|
||||
(.write w tag)
|
||||
(print-method (peref-data v) w)))
|
||||
|
||||
(defn cached [lookup-fn]
|
||||
"Takes the result of a lookup-fn and memoizes it. Also provides a function,
|
||||
accessed by looking up :clear-cache, which clears the cache completely if called
|
||||
with no arguments, and clears the cache for a particular peref when called with
|
||||
its arguments."
|
||||
(let [cache (atom {})
|
||||
clear-cache
|
||||
(fn ([] (reset! cache {}))
|
||||
([peref] (swap! cache #(dissoc % (peref-data peref)))))]
|
||||
(fn [& args]
|
||||
(if (= (first args) :clear-cache)
|
||||
(apply clear-cache (rest args))
|
||||
(if (contains? @cache args)
|
||||
(get @cache args)
|
||||
(let [v (apply lookup-fn (vec args))]
|
||||
(swap! cache #(assoc % args v))
|
||||
v))))))
|
||||
|
||||
(defn partial-map [fn-map]
|
||||
"Takes a map of the form {name fn} and returns a lookup function which
|
||||
takes a name and the arguments to pass to fn and returns the fn.
|
||||
It is legal for the value to not be a function so long as the lookup
|
||||
is not passed any extra arguments besides the name."
|
||||
(fn [name & args]
|
||||
(let [fn (get fn-map name)]
|
||||
(if fn
|
||||
(apply partial fn args)
|
||||
nil))))
|
||||
|
||||
(defn compose-lookup [& lookup-fns]
|
||||
"Returns a lookup-fn which calls each of the passed-in lookup-fns in turn,
|
||||
returning the first non-nil result."
|
||||
(fn [& args]
|
||||
(loop [lookup-fns lookup-fns]
|
||||
(if (seq lookup-fns)
|
||||
(let [lookup-fn (first lookup-fns)
|
||||
v (apply lookup-fn args)]
|
||||
(if (nil? v)
|
||||
(recur (rest lookup-fns))
|
||||
v))))))
|
||||
|
||||
(defn lookup [id & lookup-fns]
|
||||
"Creates a peref lookup function, identified by a namespaced symbol, which is
|
||||
used to create peref objects. Simply call the result with the arguments to pass
|
||||
to your lookup-fns, and it will return a peref object that will call your lookup-fns
|
||||
when derefed."
|
||||
(->PerefLookup id (apply compose-lookup lookup-fns)))
|
7
test/exav/core_test.clj
Normal file
7
test/exav/core_test.clj
Normal file
|
@ -0,0 +1,7 @@
|
|||
(ns exav.core-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[exav.core :refer :all]))
|
||||
|
||||
(deftest a-test
|
||||
(testing "FIXME, I fail."
|
||||
(is (= 0 1))))
|
Loading…
Reference in a new issue