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