The swap! function, one of the most idiomatic tools in the Clojure toolbox, does instance? checking. We are told in programming to avoid implementing conditionals around type checking, to prefer polymorphism (protocols). It seems odd that ClojureScript does not implement the ISwap protocol directly against atoms and does instead in the public swap! api falling back on the protocol only after checking if the subject is an atom.
I assume this tactic must have been used for performance reasons since atoms are the primary use case for swap! and numerous other atomic methods. Is this right?
I would have preferred to implement an atom's api as part of the actual protocol so that this sort of thing would have been unnecessary.
(defn swap!
"Atomically swaps the value of atom to be:
(apply f current-value-of-atom args). Note that f may be called
multiple times, and thus should be free of side effects. Returns
the value that was swapped in."
([a f]
(if (instance? Atom a)
(reset! a (f (.-state a)))
(-swap! a f)))
([a f x]
(if (instance? Atom a)
(reset! a (f (.-state a) x))
(-swap! a f x)))
([a f x y]
(if (instance? Atom a)
(reset! a (f (.-state a) x y))
(-swap! a f x y)))
([a f x y & more]
(if (instance? Atom a)
(reset! a (apply f (.-state a) x y more))
(-swap! a f x y more))))
It looks like it is performance related: http://dev.clojure.org/jira/browse/CLJS-760
Add an IAtom protocol with a -reset! method and a fast path for Atom in cljs.core/reset!.
See jsperf here - http://jsperf.com/iatom-adv
Latest chrome and firefox versions suffer ~20-30% slowdown. Older firefox versions suffer up to 60-70%.
Further down the ticket, it was decided to split IAtom into two protocols: IReset and ISwap. But this was the implementation that David went with, which does the type checking, and I imagine is was done to get the speed back up.
Unfortunately, it's not clear why Atom wasn't made to implement IReset (and ISwap) for that matter, nor why those things weren't looked for instead. And it's not clear how the original patch worked either. It basically took the implementation of reset! and put it under an instance check, and added the -reset! path for it:
diff --git a/src/cljs/cljs/core.cljs b/src/cljs/cljs/core.cljs
index 9fed929..c6e41ab 100644
--- a/src/cljs/cljs/core.cljs
+++ b/src/cljs/cljs/core.cljs
## -7039,6 +7039,9 ## reduces them without incurring seq initialization"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;;
+(defprotocol IAtom
+ (-reset! [o new-value]))
+
(deftype Atom [state meta validator watches]
IEquiv
(-equiv [o other] (identical? o other))
## -7088,14 +7091,16 ## reduces them without incurring seq initialization"
"Sets the value of atom to newval without regard for the
current value. Returns newval."
[a new-value]
+ (if (instance? Atom a)
(let [validate (.-validator a)]
(when-not (nil? validate)
- (assert (validate new-value) "Validator rejected reference state")))
+ (assert (validate new-value) "Validator rejected reference state"))
(let [old-value (.-state a)]
(set! (.-state a) new-value)
(when-not (nil? (.-watches a))
- (-notify-watches a old-value new-value)))
- new-value)
+ (-notify-watches a old-value new-value))
+ new-value))
+ (-reset! a new-value)))
(defn swap!
"Atomically swaps the value of atom to be:
This was committed in 33692b79a114faf4bedc6d9ab38d25ce6ea4b295 (or at least something very close to it). And then the other protocol changes were done in 3e6564a72dc5e175fc65c9767364d05af34e4968:
commit 3e6564a72dc5e175fc65c9767364d05af34e4968
Author: David Nolen <david.nolen#gmail.com>
Date: Mon Feb 17 14:46:10 2014 -0500
CLJS-760: break apart IAtom protocol into IReset & ISwap
diff --git a/src/cljs/cljs/core.cljs b/src/cljs/cljs/core.cljs
index 25858084..e4df4420 100644
--- a/src/cljs/cljs/core.cljs
+++ b/src/cljs/cljs/core.cljs
## -7061,9 +7061,12 ## reduces them without incurring seq initialization"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;;
-(defprotocol IAtom
+(defprotocol IReset
(-reset! [o new-value]))
+(defprotocol ISwap
+ (-swap! [o f] [o f a] [o f a b] [o f a b xs]))
+
(deftype Atom [state meta validator watches]
IEquiv
(-equiv [o other] (identical? o other))
## -7124,21 +7127,33 ## reduces them without incurring seq initialization"
new-value))
(-reset! a new-value)))
+;; generic to all refs
+;; (but currently hard-coded to atom!)
+(defn deref
+ [o]
+ (-deref o))
+
(defn swap!
"Atomically swaps the value of atom to be:
(apply f current-value-of-atom args). Note that f may be called
multiple times, and thus should be free of side effects. Returns
the value that was swapped in."
([a f]
- (reset! a (f (.-state a))))
+ (if (instance? Atom a)
+ (reset! a (f (.-state a)))
+ (-swap! a (deref a))))
([a f x]
- (reset! a (f (.-state a) x)))
+ (if (instance? Atom a)
+ (reset! a (f (.-state a) x))
+ (-swap! a (f (deref a) x))))
([a f x y]
- (reset! a (f (.-state a) x y)))
- ([a f x y z]
- (reset! a (f (.-state a) x y z)))
- ([a f x y z & more]
- (reset! a (apply f (.-state a) x y z more))))
+ (if (instance? Atom a)
+ (reset! a (f (.-state a) x y))
+ (-swap! a (f (deref a) x y))))
+ ([a f x y & more]
+ (if (instance? Atom a)
+ (reset! a (apply f (.-state a) x y more))
+ (-swap! a (f (deref a) x y more)))))
(defn compare-and-set!
"Atomically sets the value of atom to newval if and only if the
## -7149,13 +7164,6 ## reduces them without incurring seq initialization"
(do (reset! a newval) true)
false))
-;; generic to all refs
-;; (but currently hard-coded to atom!)
-
-(defn deref
- [o]
- (-deref o))
-
(defn set-validator!
"Sets the validator-fn for an atom. validator-fn must be nil or a
side-effect-free fn of one argument, which will be passed the intended
It doesn't help that the ticket is dual-natured: performance is a problem (though not clear in what way: "atoms are not operating fast enough", or "other things using reset! are not running fast enough"?) and a design issue ("we want an IAtom protocol"). I think the issue was that other implementations would have to suffer the validation and notifying watchers costs even if they aren't really atoms. I wish it were clearer.
One thing I've not liked about the commits in Clojure/Script is that they are not very descriptive. I wish they were more kernel-like with appropriate background information so that folks (like us) trying to figure out how things came to be had more useful information about it.
Related
One of my favorite ways to test the power of a language I'm learning is to try and implement various fixed-point combinators. Since I'm learning Clojure (though I'm not new to lisps), I did the same for it.
First, a little "testable" code, factorial:
(def !'
"un-fixed factorial function"
(fn [f]
(fn [n]
(if (zero? n)
1
(* n (f (dec n)))))))
(defn !
"factorial"
[n]
(if (zero? n)
1
(apply * (map inc (range n)))))
For any combinator c I implement, I want to verify that ((c !') n) is equal to (! n).
We start with the traditional Y:
(defn Y
"pure lazy Y combinator => stack overflow"
[f]
(let [A (fn [x] (f (x x)))]
(A A)))
But of course Clojure is not nearly so lazy as that, so we pivot to Z:
(defn Z
"strict fixed-point combinator"
[f]
(let [A (fn [x] (f (fn [v] ((x x) v))))]
(A A)))
And indeed, it holds that (= ((Z !') n) (! n)).
Now comes my issue: I cannot get either of U or the Turing combinator (theta-v) to work correctly. I suspect with U it's a language limit, while with theta-v I'm more inclined to believe it's a misread of Wikipedia's notation:
(defn U
"the U combinator => broken???"
[f]
(f f))
(defn theta-v
"Turing fixed-point combinator by-value"
[f]
(let [A (fn [x] (fn [y] (y (fn [z] ((x x) y) z))))]
(A A)))
A sample REPL experience:
((U !') 5)
;=> Execution error (ClassCastException) at fix/!'$fn (fix.clj:55).
;=> fix$_BANG__SINGLEQUOTE_$fn__180 cannot be cast to java.lang.Number
((theta-v !') 5)
;=> Execution error (ClassCastException) at fix/theta-v$A$fn (fix.clj:36).
;=> java.lang.Long cannot be cast to clojure.lang.IFn
Can anyone explain
Why these implementations of U and theta-v are not working; and
How to fix them?
Your definition of theta-v is wrong for two reasons. The first is pretty obvious: you accept f as a parameter and then ignore it. A more faithful translation would be to use def style, as you have for your other functions:
(def theta-v
"Turing fixed-point combinator by-value"
(let [A (fn [x] (fn [y] (y (fn [z] ((x x) y) z))))]
(A A)))
The second reason is a bit sneakier: you translated λz.xxyz to (fn [z] ((x x) y) z), remembering that lisps need more parentheses to denote function calls that are implicit in lambda-calculus notation. However, you missed one set: just as x x y z would have meant "evaluate x twice, then y once, then finally return z", what you wrote means "evaluate ((x x) y), then throw away that result and return z". Adding the extra set of parentheses yields a working theta-v:
(def theta-v
"Turing fixed-point combinator by-value"
(let [A (fn [x] (fn [y] (y (fn [z] (((x x) y) z)))))]
(A A)))
and we can demonstrate that it works by calculating some factorials:
user> (map (theta-v !') (range 10))
(1 1 2 6 24 120 720 5040 40320 362880)
As for U: to use the U combinator, functions being combined must change how they self-call, meaning you would need to rewrite !' as well:
(def U [f] (f f))
(def ! (U (fn [f]
(fn [n]
(if (zero? n)
1
(* n ((f f) (dec n))))))))
Note that I have changed (f (dec n)) to ((f f) (dec n)).
I like my code to have a "top-down" structure, and that means I want to do exactly the opposite from what is natural in Clojure: functions being defined before they are used. This shouldn't be a problem, though, because I could theoretically declare all my functions first, and just go on and enjoy life. But it seems in practice declare cannot solve every single problem, and I would like to understand what is exactly the reason the following code does not work.
I have two functions, and I want to define a third by composing the two. The following three pieces of code accomplish this:
1
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(defn mycomp [x] (f (g x)))
(println (mycomp 10))
2
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(def mycomp (comp f g))
3
(declare f g)
(defn mycomp [x] (f (g x)))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
But what I would really like to write is
(declare f g)
(def mycomp (comp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
And that gives me
Exception in thread "main" java.lang.IllegalStateException: Attempting to call unbound fn: #'user/g,
That would mean forward declaring works for many situations, but there are still some cases I can't just declare all my functions and write the code in any way and in whatever order I like. What is the reason for this error? What does forward declaring really allows me to do, and what are the situations I must have the function already defined, such as for using comp in this case? How can I tell when the definition is strictly necessary?
You can accomplish your goal if you take advantage of Clojure's (poorly documented) var behavior:
(declare f g)
(def mycomp (comp #'f #'g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(mycomp 10) => 45
Note that the syntax #'f is just shorthand (technically a "reader macro") that translates into (var f). So you could write this directly:
(def mycomp (comp (var f) (var g)))
and get the same result.
Please see this answer for a more detailed answer on the (mostly hidden) interaction between a Clojure symbol, such as f, and the (anonymous) Clojure var that the symbol points to, namely either #'f or (var f). The var, in turn, then points to a value (such as your function (fn [x] (* x 3)).
When you write an expression like (f 10), there is a 2-step indirection at work. First, the symbol f is "evaluated" to find the associated var, then the var is "evaluated" to find the associated function. Most Clojure users are not really aware that this 2-step process exists, and nearly all of the time we can pretend that there is a direct connection between the symbol f and the function value (fn [x] (* x 3)).
The specific reason your original code doesn't work is that
(declare f g)
creates 2 "empty" vars. Just as (def x) creates an association between the symbol x and an empty var, that is what your declare does. Thus, when the comp function tries to extract the values from f and g, there is nothing present: the vars exist but they are empty.
P.S.
There is an exception to the above. If you have a let form or similar, there is no var involved:
(let [x 5
y (* 2 x) ]
y)
;=> 10
In the let form, there is no var present. Instead, the compiler makes a direct connection between a symbol and its associated value; i.e. x => 5 and y => 10.
I think Alan's answer addresses your questions very well. Your third example works because you aren't passing the functions as arguments to mycomp. I'd reconsider trying to define things in "reverse" order because it works against the basic language design, requires more code, and might be harder for others to understand.
But... just for laughs and to demonstrate what's possible with Clojure macros, here's an alternative (worse) implementation of comp that works for your preferred syntax, without dealing directly in vars:
(defn- comp-fn-arity [variadic? args f & fs] ;; emits a ([x] (f (g x)) like form
(let [args-vec (if variadic?
(into (vec (butlast args)) ['& (last args)])
(apply vector args))
body (reduce #(list %2 %1)
(if variadic?
(apply list 'apply (last fs) args)
(apply list (last fs) args))
(reverse (cons f (butlast fs))))]
`(~args-vec ~body)))
(defmacro momp
([] identity)
([f] f)
([f & fs]
(let [num-arities 5
args-syms (repeatedly num-arities gensym)]
`(fn ~#(map #(apply comp-fn-arity (= % (dec num-arities)) (take % args-syms) f fs)
(range num-arities))))))
This will emit something kinda like comp's implementation:
(macroexpand '(momp f g))
=>
(fn*
([] (f (g)))
([G__1713] (f (g G__1713)))
([G__1713 G__1714] (f (g G__1713 G__1714)))
([G__1713 G__1714 G__1715] (f (g G__1713 G__1714 G__1715)))
([G__1713 G__1714 G__1715 & G__1716] (f (apply g G__1713 G__1714 G__1715 G__1716))))
This works because your (unbound) functions aren't being passed as values to another function; during compilation the macro expands "in place" as if you'd written the composing function by hand, as in your third example.
(declare f g)
(def mycomp (momp f g))
(defn f [x] (* x 3))
(defn g [x] (+ x 5))
(mycomp 10) ;; => 45
(apply (momp vec reverse list) (range 10)) ;; => [9 8 7 6 5 4 3 2 1 0]
This won't work in some other cases, e.g. ((momp - dec) 1) fails because dec gets inlined and doesn't have a 0-arg arity to match the macro's 0-arg arity. Again, this is just for the sake of example and I wouldn't recommend it.
I've been reading SICP and getting into lisps / clojure more and more, and I found myself wondering how apply would actually be implemented. Of course there are some silly ways like (defn apply [f xs] (eval (cons f xs))), but I can't find an example to look at covering the real implementation. I figured once I got to 4.1 in SICP it would be covered, but was disappointed to find out that they define apply in terms of the already existing underlying scheme implementation.
How would one go about implementing this from the ground up?
EDIT:
I think the way I asked this is a bit unclear. I know how apply is implemented in terms of the eval/apply interaction mentioned in SICP. What I'm referring to is the underlying apply in scheme that they fall back on within the definition of the metacircular version of apply. Basically ... how to call a function with a list of args, each passed individually, if you don't already have apply implemented in some base language.
Due to Clojure being hosted on the JVM platform (and being designed to have great Java interop), the peculiarities of the underlying platform shine through.
You can see in the source code for apply on JVM here: https://github.com/clojure/clojure/blob/clojure-1.9.0/src/clj/clojure/core.clj#L652
Notice how there is specific code for arities up to 4, for efficiency reasons.
Arities 5 and above are treated in a less efficient way.
(defn apply
"Applies fn f to the argument list formed by prepending intervening arguments to args."
{:added "1.0"
:static true}
([^clojure.lang.IFn f args]
(. f (applyTo (seq args))))
([^clojure.lang.IFn f x args]
(. f (applyTo (list* x args))))
([^clojure.lang.IFn f x y args]
(. f (applyTo (list* x y args))))
([^clojure.lang.IFn f x y z args]
(. f (applyTo (list* x y z args))))
([^clojure.lang.IFn f a b c d & args]
(. f (applyTo (cons a (cons b (cons c (cons d (spread args)))))))))
The ClojureScript implementation does the same, but looks quite different from the JVM implementation above:
(defn apply
"Applies fn f to the argument list formed by prepending intervening arguments to args."
([f args]
(if (.-cljs$lang$applyTo f)
(let [fixed-arity (.-cljs$lang$maxFixedArity f)
bc (bounded-count (inc fixed-arity) args)]
(if (<= bc fixed-arity)
(apply-to f bc args)
(.cljs$lang$applyTo f args)))
(apply-to-simple f (seq args))))
([f x args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (inc (bounded-count fixed-arity args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x (seq args))))
([f x y args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x y args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 2 (bounded-count (dec fixed-arity) args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x y (seq args))))
([f x y z args]
(if (.-cljs$lang$applyTo f)
(let [arglist (list* x y z args)
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 3 (bounded-count (- fixed-arity 2) args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f x y z (seq args))))
([f a b c d & args]
(if (.-cljs$lang$applyTo f)
(let [spread-args (spread args)
arglist (cons a (cons b (cons c (cons d spread-args))))
fixed-arity (.-cljs$lang$maxFixedArity f)
bc (+ 4 (bounded-count (- fixed-arity 3) spread-args))]
(if (<= bc fixed-arity)
(apply-to f bc arglist)
(.cljs$lang$applyTo f arglist)))
(apply-to-simple f a b c d (spread args)))))
I made a dynamic lisp language a while ago and I didn't expose apply. I did supply rest arguments and thus since I had eval and macros in the language I made several attempts to do this. I found out quickly that macros are useless so eval is the only solution. Your example has a flaw:
(defn mapply [f xs] (eval (cons f xs)))
(mapply cons '(1 (3)))
; ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn
The reason is that the resulting expression being evaluated by eval becomes:
(cons 1 (3))
Instead of
(cons '1 '(3))
Thus to mimic it you need to make sure the already evaluated values doesn't get evaluates a second time around. We could fix that by quoting the values:
(defn m2apply [f xs] (eval (cons f (map #(list 'quote %) xs))))
(m2apply cons '(1 (3)))
; ==> (1 3)
Yey.. But you really are doing a lot more computing than you need. For a lexical interpreter that does have apply you only need to leak that as a primitive into the environment. And yes, it is the unimpressive apply whose only purpose is to call internals (primitives) and to evaluate user function bodies in an extended environment. In a language not already a lisp the apply and a whole set of primitives and data structures would be implemented in the implementation language and it would just expose that instead.
The way you implement apply is directly tied to how you implement function calls. If you compile your code, you have a protocol at runtime where you know how values are exchanged between function calls, and apply can emit code that satisfy to this protocol. We could do the same in a quick and dirty interpreter. Let's define a package:
(defpackage :interpreter (:use :cl))
(in-package :interpreter)
We define a function object, which has an optional name, a list of parameters, the code as well as a set of bindings being closed-over:
(defstruct fn name parameters code closed)
We also define a frame, which has a set of bindings and an optional parent frame:
(defstruct frame bindings parent)
Here we have a simple interpreter, and we put the current frame within the evaluation environment:
(defstruct env frame)
Bindings are either objects of type FN, or cons pairs. We write generic functions to manipulate them with a uniform API. Functions and variables share the same namespace:
(defgeneric name (object)
(:method ((fn fn)) (fn-name fn))
(:method ((pair cons)) (car pair)))
(defgeneric value (object)
(:method ((c cons)) (cdr c))
(:method ((fn fn)) fn))
We define two functions, my-apply and my-eval
(declaim (ftype function my-apply my-eval))
There is a global environment, which is simply:
(defparameter *global-frame*
(make-frame
:bindings (list (make-fn :name '+
:parameters '(x y)
;; built-in
:code (lambda (x y) (+ x y)))
(make-fn :name 'addition
:parameters '(x y)
:code '(+ x y)))
:parent nil))
The empty environment implicitly holds to the global frame:
(defgeneric frame (env)
(:method ((empty null)) *global-frame*)
(:method ((env env)) (env-frame env)))
Resolving a binding involves visiting parent frames:
(defun resolve (name frame &optional (on-error :error))
(labels ((recurse (frame)
(cond
(frame (or (find name (frame-bindings frame) :key #'name)
(recurse (frame-parent frame))))
((eql :error on-error) (error "Unknown: ~a" name)))))
(recurse frame)))
The evaluation function is the following one:
(defun my-eval (code env &aux (frame (frame env)))
(flet ((ev (exp) (my-eval exp env)))
(typecase code
(symbol (value (resolve code frame)))
(atom code)
(cons
(destructuring-bind (head . tail) code
(case head
(list (mapcar #'ev tail))
(let (destructuring-bind ((var val) expr) tail
(my-eval expr
(make-env :frame (make-frame :bindings `((,var . ,(ev val)))
:parent frame)))))
(thunk (make-fn :name nil
:parameters nil
:code (first tail)
:closed (frame-bindings frame)))
(apply (my-apply (ev (first tail))
(ev (second tail))
env))
(t (my-apply (resolve head (frame env))
(mapcar #'ev tail)
env))))))))
The evaluation functions accept the following terms:
(list <...>) builds a list containing the result of evaluation of its arguments
(apply <fn-expr> <arg-expr>), evaluate all arguments and call the my-apply primitive.
(let (<var> <val>) <expr>), local binding
(thunk <expr>) closes over current environment and produce an anonymous closure with no parameters which returns the value of <expr>
(<f> . <args>) function call
symbols are resolved for values, and other values are returned as-is.
The built-in my-apply knows how to bind parameters to values dynamically:
(defun my-apply (fn arguments env)
(assert (= (length arguments)
(length (fn-parameters fn)))
()
"Length mismatch when calling ~S with argsuments ~S"
fn
arguments)
(let ((code (fn-code fn)))
(typecase code
(function (apply code arguments))
(t (my-eval code
(make-env :frame
(make-frame :bindings (append (fn-closed fn)
(mapcar #'cons
(fn-parameters fn)
arguments))
:parent (frame env))))))))
For example:
(my-eval '(let (f (let (x 10) (thunk (addition x 5))))
(let (x 20) (apply f (list)))) nil)
=> 15
In the above example, f is a function that closes over the binding of x to 10, and calls addition. The binding that is made later is not seen by the closure. The call to apply resolves f and builds an empty list. The call to addition resolves to (+ 10 5), which itself eventually calls the CL function +. You can (trace my-eval) to see how things are evaluated. The above code is a bit messy.
I don't think you can define it from the ground up in the language: at some point your language needs a mechanism of actually calling a function on a bunch of arguments, and apply is pretty much that point.
That's why it's a primitive: asking how you implement apply is like asking how you implement cons or +: sooner or later the thing needs to bottom out and you call a function which is not defined in the language, or is only partly defined in the language: + for instance can probably be partly implemented in terms of checking types and extracting the actual machine numbers from them, but sooner or later you are going to ask the machine to add some machine numbers for you (or, OK, some equivalent operation if your machine does not support addition directly).
The core.clj code for Clojure itself (available at https://github.com/clojure/clojure/blob/clojure-1.7.0/src/clj/clojure/core.clj) gives the following definition for comp:
(defn comp
"Takes a set of functions and returns a fn that is the composition
of those fns. The returned fn takes a variable number of args,
applies the rightmost of fns to the args, the next
fn (right-to-left) to the result, etc."
{:added "1.0"
:static true}
([] identity)
([f] f)
([f g]
(fn
([] (f (g)))
([x] (f (g x)))
([x y] (f (g x y)))
([x y z] (f (g x y z)))
([x y z & args] (f (apply g x y z args)))))
([f g & fs]
(reduce1 comp (list* f g fs))))
I'm new to Clojure and trying to understand both the technical side and idiomatic style sides of it and I'm wondering what the reason is for including so many cases when two functions are passed into comp. Why bother with the [x y] and [x y z] cases at all?
As Mars said, this is done for efficiency. Dispatching directly is faster than using apply. Unrolling functions like this is quite common to speed up the performance, juxt is unrolled in a similar fashion.
I've started learning core.logic and I'm totally lost. I am trying to write a core.logic relation which refactors an expression, renaming symbols. I want a relation that returns for a given expression, list of symbols and a list of symbols to rename those symbols:
(defn rename [exp from to]...
the expression with all the symbols in from becoming the corresponding one in to:
e.g. (rename '(defn multiply [x y] (* x y)) [x y] [a b])
returns (defn multiply [a b] (* a b))
but it needs to be aware of scope,
so (rename '(defn q [x] ((fn [x] (* x 5)) x)) [x] [a])
would return (defn q [a] ((fn [x] (* x 5)) a))
I don't know where to start solving this - any hints would be greatly appreciated!
This problem is more suitable for FP as it is just a tree traversal and replace operation, where as LP is more about specifying constrains and asking all possible solution around those constrains for a specific input. But if you really want to do this logical way, I tried something that does try to do it LP way but it doesn't handle a lot of cases and is just a starting point.
(defrel replace-with a b)
(fact replace-with 'x 'a)
(fact replace-with 'y 'b)
(defn replace [a b]
(conde
[(replace-with a b)]
[(== a b)]))
(defn replace-list [from to]
(conde
[(== from []) (== to [])]
[(fresh [f t f-rest t-rest]
(resto from f-rest)
(resto to t-rest)
(firsto from f) (firsto to t)
(conda [(replace-list f t)]
[(replace f t)])
(replace-list f-rest t-rest))]))
(first (run 1 [q]
(fresh [from]
(== from '(defn multiply [x y] (* x y)))
(replace-list from q))))
==> (defn multiply (a b) (* a b))