redefining multimethods at the repl

1,736 views
Skip to first unread message

Brian Marick

unread,
Sep 4, 2012, 6:31:45 PM9/4/12
to clo...@googlegroups.com
I'm trying to write exercises for multimethods. Book readers will be working at the repl. Multimethods are stateful in a bad way, as shown below. Is there some sort of trick to using multimethods at the repl, or should I just give up on exercises using them?

;; Two types:
user=> (defn ship [name] (with-meta {:name name} {:type :ship}))
user=> (defn asteroid [name] (with-meta {:name name} {:type :asteroid}))

;; The dispatch function and defmulti

user=> (def classify-colliding-things
(fn [thing1 thing2]
[(type thing1) (type thing2)]))
user=> (defmulti collide classify-colliding-things)

;; Actually, since the arguments can come in any order, it'd be better to sort the types:

user=> (def classify-colliding-things
(fn [thing1 thing2]
(sort [(type thing1) (type thing2)])))

;; And let's redefine the multimethod to use the new comparison function.

user=> (defmulti collide classify-colliding-things)

;; OK, now we define the methods.

user=> (defmethod collide [:asteroid :ship]
[& things]
"collide asteroid to ship")

;;; And use them with great confidence:

user=> (collide (ship "Space Beagle") (asteroid "Malse"))
IllegalArgumentException No method in multimethod 'collide' for dispatch value: [:ship :asteroid] clojure.lang.MultiFn.getFn (MultiFn.java:121)

;;; The redefinition didn't take

-----
Brian Marick, Artisanal Labrador
Contract programming in Ruby and Clojure
Occasional consulting on Agile
Writing /Functional Programming for the Object-Oriented Programmer/: https://leanpub.com/fp-oo


Stephen Compall

unread,
Sep 4, 2012, 6:35:09 PM9/4/12
to clo...@googlegroups.com
On Tue, 2012-09-04 at 17:31 -0500, Brian Marick wrote:
> user=> (defmulti collide classify-colliding-things)

If you're okay with a little handwaving, how about

(defmulti collide #'classify-colliding-things)

Now there is no need to rerun defmulti.

--
Stephen Compall
"^aCollection allSatisfy: [:each | aCondition]": less is better than


Jack Moffitt

unread,
Sep 4, 2012, 6:35:25 PM9/4/12
to clo...@googlegroups.com
> user=> (defmulti collide classify-colliding-things)

You want (defmulti collide #'classify-colliding-things)

Binding to the var instead of the value will allow it to be udpated.

jack.

Ulises

unread,
Sep 4, 2012, 6:41:18 PM9/4/12
to clo...@googlegroups.com
> Binding to the var instead of the value will allow it to be udpated.

Alternatively you could ns-unmap the multimethod before redefining it.

U

Brian Marick

unread,
Sep 5, 2012, 11:09:19 AM9/5/12
to clo...@googlegroups.com
Thanks. I think I'll write my own `defgeneric` to hide an `ns-unmap` from the reader. (I like the terminology of "generic function" better than multimethod anyway, as an introduction to the idea.)
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Stuart Halloway

unread,
Sep 5, 2012, 12:58:46 PM9/5/12
to clo...@googlegroups.com
Brian,

I share  your pain. A standardized contrib solution for this would be welcomed.

Are there other things like this that cause people to "restart REPL" unnecessarily? I would like to identify the whole list of such things and kill them.

Stu

Hugo Duncan

unread,
Sep 5, 2012, 1:14:09 PM9/5/12
to clo...@googlegroups.com
Stuart Halloway <stuart....@gmail.com> writes:

> Are there other things like this that cause people to "restart REPL" unnecessarily? I would like to identify the whole list of such things and kill them.

Maybe related. Ritz has slime-load-file hooked up to code that tries to
remove dead vars from a namespace. i.e. if you remove a var from your
source and reload the file, it will notice that the var is no longer
there and remove it.

It does this by setting a flag in the metadata of every var in the
namespace before loading, then loads the file, then examines which vars
still have the flag set.

This works very well except for defonce'd vars, which are removed by the
above logic. One way to solve this would be for defonce, and any other
def form with the same semantics, to set a marker keyword, :defonce say,
on the metadata of the vars they define.

Hugo

Kevin Downey

unread,
Sep 5, 2012, 1:15:13 PM9/5/12
to clo...@googlegroups.com
if I recall, the current defonce like behavior of multimethods was a
response to the situation where if you have your multimethods split
across multiple files reloading the file with the defmulti in it would
re-def the multimethod with the new dispatch, but it would not have
any of the methods loaded from the other files.

oscillating between these poles at about 6.31139e7hz seems less than ideal.

maybe defmethod could try and capture the ns a method is defined in,
and defmulti would try to force a reload? seems overly complicated for
core.
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Phil Hagelberg

unread,
Sep 5, 2012, 5:13:35 PM9/5/12
to clo...@googlegroups.com
Kevin Downey <red...@gmail.com> writes:

> if I recall, the current defonce like behavior of multimethods was a
> response to the situation where if you have your multimethods split
> across multiple files reloading the file with the defmulti in it would
> re-def the multimethod with the new dispatch, but it would not have
> any of the methods loaded from the other files.
>
> oscillating between these poles at about 6.31139e7hz seems less than ideal.
>
> maybe defmethod could try and capture the ns a method is defined in,
> and defmulti would try to force a reload? seems overly complicated for
> core.

I assume there's already a really good reason re-evaling defmulti
doesn't just change the dispatch function without blowing away the
method table? Though I can't think of one myself.

-Phil

Stuart Halloway

unread,
Sep 5, 2012, 5:34:09 PM9/5/12
to clo...@googlegroups.com
I started a wiki page for this:


If you have other REPL-reloading annoyances please add them there.

Stuart Halloway
Clojure/core
http://clojure.com

Stuart Sierra

unread,
Sep 5, 2012, 9:15:07 PM9/5/12
to clo...@googlegroups.com
This is what I started working on tools.namespace to solve.

I came to the conclusion that it's impossible to make something that works in 100% of all cases, but I'm hoping to get to 90%.

I added some notes to the wiki page too.
-S

Brian Marick

unread,
Sep 5, 2012, 10:16:28 PM9/5/12
to clo...@googlegroups.com

On Sep 5, 2012, at 11:58 AM, Stuart Halloway wrote:

> I share your pain. A standardized contrib solution for this would be welcomed.
>

What I'm doing will be somewhat specific to the chapter in the book. But if it matters, it will be here:

https://github.com/marick/fp-oo/blob/master/sources/generic.clj
https://github.com/marick/fp-oo/blob/master/test/sources/t_generic.clj

I've already discovered changes I need to make.

Laurent PETIT

unread,
Sep 7, 2012, 10:56:56 AM9/7/12
to clo...@googlegroups.com
2012/9/5 Stuart Halloway <stuart....@gmail.com>:
> I started a wiki page for this:
>
> http://dev.clojure.org/display/design/Never+Close+a+REPL
>
> If you have other REPL-reloading annoyances please add them there.

Adding new dependencies to my Leiningen project.

Solved by pomegranate ?

I think I could add an experimental feature to CounterClockWise so
that whenever the dependencies change, all the new dependencies are
optimistically propagated to open REPLs associated with the project
(maybe asking a user confirmation first).

Nelson Morris

unread,
Sep 7, 2012, 11:46:28 AM9/7/12
to clo...@googlegroups.com
Pomegranate can download dependencies and blindly add them to the
classpath. It doesn't include any logic to track what jars are
already there, so might add the same ones twice on successive calls.
Also it doesn't help for moving between dependency trees, which might
change a version. This is probably where "optimistically" comes in.

A library built on top that can handle this stuff would be awesome.

Phil Hagelberg

unread,
Sep 7, 2012, 7:55:54 PM9/7/12
to clo...@googlegroups.com
Stuart Halloway <stuart....@gmail.com> writes:

> I started a wiki page for this:
>
> http://dev.clojure.org/display/design/Never+Close+a+REPL
>

> I believe these problems could be entirely solved in a contrib library
> of helper functions for REPL development. This a significantly better
> than making changes to Clojure, which must pass a high bar and be
> carefully assessed for possible affects on exiting code.

In the case of multimethods specifically, isn't this just a band-aid
workaround that doesn't address the actual problem?

-Phil

Denis Labaye

unread,
Sep 8, 2012, 4:03:40 AM9/8/12
to clo...@googlegroups.com
On Wed, Sep 5, 2012 at 12:31 AM, Brian Marick <mar...@exampler.com> wrote:
I'm trying to write exercises for multimethods. Book readers will be working at the repl. Multimethods are stateful in a bad way, as shown below. Is there some sort of trick to using multimethods at the repl, or should I just give up on exercises using them?

;; Two types:
user=> (defn ship [name] (with-meta {:name name} {:type :ship}))
user=> (defn asteroid [name] (with-meta {:name name} {:type :asteroid}))

;; The dispatch function and defmulti

user=> (def classify-colliding-things
            (fn [thing1 thing2]
              [(type thing1) (type thing2)]))
user=> (defmulti collide classify-colliding-things)

;; Actually, since the arguments can come in any order, it'd be better to sort the types:

user=> (def classify-colliding-things
            (fn [thing1 thing2]
              (sort [(type thing1) (type thing2)])))

;; And let's redefine the multimethod to use the new comparison function.

user=> (defmulti collide classify-colliding-things)

;; OK, now we define the methods.

user=> (defmethod collide [:asteroid :ship]
         [& things]
         "collide asteroid to ship")

;;; And use them with great confidence:

user=> (collide (ship "Space Beagle") (asteroid "Malse"))
IllegalArgumentException No method in multimethod 'collide' for dispatch value: [:ship :asteroid]  clojure.lang.MultiFn.getFn (MultiFn.java:121)

;;; The redefinition didn't take

here is a hack: define a var with the multimethod name:  

user> (def collide nil)
; #'user/collide
user> (defmulti collide classify-colliding-things)
; #'user/collide
user> (defmethod collide [:asteroid :ship]
        [& things]
        "collide asteroid to ship")
; #<MultiFn clojure.lang.MultiFn@9fe5c5>
user> (collide (ship "Space Beagle") (asteroid "Malse"))
; "collide asteroid to ship"
 

When writting multimethod I always preceed their definitions like this: 

; remove existing definition
(def mymulti nil)
(defmulti mymulti ...)


Also, I "heard" that this problem don't exists in nRepl (?)

Denis





-----
Brian Marick, Artisanal Labrador
Contract programming in Ruby and Clojure
Occasional consulting on Agile
Writing /Functional Programming for the Object-Oriented Programmer/: https://leanpub.com/fp-oo

Raymond McDermott

unread,
Sep 20, 2017, 8:27:58 AM9/20/17
to Clojure
I was bitten by this multi-method problem this week and so, coincidentally was a newbie friend of mine.

This trick works perfectly and got me (us) out of the hole.

Given the recent (very worthy IMHO) marketing efforts for the REPL can we have an update on what mitigations are in place for this and other re-loading REPL issues?

I looked on the page that Stu created ago and it seems nothing has been updated since 2014.

Does anyone know of a solid resource where we can find a clear definition of the issues the require a REPL restart and their current / best mitigations?

I have also opened a document issue on the Clojure.org web site requesting that it should have a REPL guide, which should include these gotchas. You can see more there https://github.com/clojure/clojure-site/issues/218

Ray

Matching Socks

unread,
Sep 20, 2017, 6:32:22 PM9/20/17
to Clojure
After moving a clj file and updating its ns declaration, subsequently updating a ns form that :require's it causes the REPL to bark.  Restarting the REPL recovers from it.  

Here is a self-contained demo.  Instead of making a ns to move, and moving it, I illustrate by recycling the same ns require alias on two Clojure core namespaces... which makes the REPL's perspective pretty clear:

--8<--

user=> (ns org.draintheaquifers.pickle (:require [clojure.string :as a]))
nil
org.draintheaquifers.pickle=> (ns org.draintheaquifers.pickle (:require [clojure.set :as a]))

IllegalStateException Alias a already exists in namespace org.draintheaquifers.pickle, aliasing clojure.string  clojure.lang.Namespace.addAlias (Namespace.java:224)

--8<--

Stand-alone (require ...) forms are incremental, but (ns) is usually all-encompassing. I think I would prefer ns to remap the given aliases without complaint.

John Alan McDonald

unread,
Sep 20, 2017, 7:51:13 PM9/20/17
to Clojure
It seems to me that defmulti should create a new instance of MultiFn, initialized with the method tables from the existing MultiFn, if any.
Then alter-var-root to update the Var holding the MultiFn.

A possible problem is that a new dispatch function might be inconsistent with the existing keys and values in the method tables.
You can fix that, with some pain by hand, with remove-all-methods, or remove-method.

In any case, I think this possible inconsistency is less likely a problem than similar issues that may arise from sharing the global hierarchy.
Reply all
Reply to author
Forward
0 new messages