killme2008 / defun

A macro to define clojure functions with parameter pattern matching just like erlang or elixir.
Eclipse Public License 1.0
477 stars 21 forks source link

Dynamic-dispatch match funs? #15

Closed metasoarous closed 7 years ago

metasoarous commented 8 years ago

This idea may fall outside the scope of the vision for defun, and if so that's fine. But I'd really like the ability to call (defun my-fun ...) and then later call (add-match my-fun ...) to extend the match spec.

The semantics here may be tricky to get just right (like, flexibly setting match priority when you add, being able to add or override defaults, etc...). But it seems this would help close the gap between the expressiveness of defun/match and the extensibility of multimethods.

My particular interest has to do with this idea I've been working on called datview (you can learn more about it by watching my recent Clojure/West talk https://www.youtube.com/watch?v=aI0zVzzoK_E). The idea is to base the rendering of UI on Datomic pull data and schema metadata in a flexible extensible manner. All I've been able to work out so far is some pretty basic/rigid UI helper functions that may be tolerable for very preliminary scaffolding, but that would need to be scrapped right away. If we have the ability to extend how differently shaped pull data should render (vs the defaults) based on some pattern, you'd have something that could really grow with a wider range of problems. Also, it's worth noting that multimethods don't work well as reagent components anyway, because adding a method to a multimethod doesn't update the multimethod var (so reagent doesn't know to rerender...). So this would solve more general problems for reagent users as well.

If you don't feel this is in scope for defun, I'll see if I can build it out myself using whatever work of yours I might find applicable. But if you are keen to add this functionality, I'd be happy to do most of the work building it out (though help is always appreciated).

Cheers

killme2008 commented 8 years ago

It's interesting. It's just like defmethod for defmulti to extend the multi methods.I think i can try to implement it.

metasoarous commented 8 years ago

Hi @killme2008

I've started working on this in a fork: https://github.com/metasoarous/dynamatch

Unfortunately, when I didn't hear back from you sooner I figured you weren't interested in having this be part of your project. As such I disconnected the repo as a fork and renamed the project to emphasize the different focus of the project. Of course, it's all open source, so if you'd like to fold it back into defun, you're welcome to :-)

I should mention that getting this idea right is proving to be a lot more challenging than I initially suspected. In particular, getting the semantics right regarding order of macro execution and resulting clause order is rather challenging. For instance, if someone reloads the namespace with the initial definition, does it wipe out all of the added matches you defined? What if you execute addmatches! on two different clause stacks, and then re-evaluate the first one? Does it override the second? What if you added some clauses to the first one and re-evaluate; do the new clauses end up arranged together with the other clauses in that addmatches! call, or at the top? In general, how do you control the order of where your clauses get added? How do you handle defaults, and make sure they play nice with flexibility in where you place your clauses? How do you override predefined clauses? Can of worms...

My solution to all of these problems has been to assign unique identity keywords to each block of clauses added via addmatches! and arrange things so that blocks of clauses defined via addmatches! remain contiguous. Additionally, once a block has been added to the stack, it's relative position in the stack is fixed upon re-evaluation of the forms. (Thought perhaps I'll add other functions for reordering if needed.) I'm also making it possible to name individual clauses via metadata so they can be overridden.

metasoarous commented 8 years ago

FWIW, I updated the README. Much more readable now...

In any case, I've realized there's a rather terrible limitation to all this with respect to targeting cljs. To get fully dynamic functionality, I had to use eval, which of course isn't supported in cljs. As such, the only way I can think to get this to work "properly" in cljs is to have a more static implementation of the core macros defun and addmatches! that uses static, compile-time var metadata to track the match clauses. However, this means that the process compiling the macro (generally clj) would have to be able to access the static var metadata, and I have not been able to figure out whether this is possible or not :-/ I've opened a Stack Overflow question on this in case anyone has any bright ideas: http://stackoverflow.com/questions/37078457/how-to-access-compile-time-cljs-var-metadata-from-a-clj-macro#.

My next best bet is to "fake" it; Instead of compiling a single match matrix based on all of the clauses, just compose the separate match block functions into something that's functionally equivalent. This will generally be less performant, but will at least will be relatively easy to implement.