Scheme -> Clojure: multimethods with predicates in the methods?
Asked Answered
B

1

12

I'm converting some Scheme code to Clojure. The original uses a dispatching pattern that's very similar to multimethods, but with an inverted approach to the matching predicates. For example, there a generic function "assign-operations". The precise implementation details aren't too important at the moment, but notice that it can take a list of argument-predicates.

(define (assign-operation operator handler . argument-predicates)
  (let ((record
         (let ((record (get-operator-record operator))
               (arity (length argument-predicates)))
           (if record
               (begin
                 (if (not (fix:= arity (operator-record-arity record)))
                     (error "Incorrect operator arity:" operator))
                 record)
               (let ((record (make-operator-record arity)))
                 (hash-table/put! *generic-operator-table* operator record)
                 record)))))
    (set-operator-record-tree! record
                               (bind-in-tree argument-predicates
                                             handler
                                             (operator-record-tree record)))))

The dispatched functions supply these predicates, one per argument in the arity of the function.

(assign-operation 'merge
  (lambda (content increment) content)
  any? nothing?)

(assign-operation 'merge
  (lambda (content increment) increment)
  nothing? any?)

(assign-operation 'merge
  (lambda (content increment)
    (let ((new-range (intersect-intervals content increment)))
      (cond ((interval-equal? new-range content) content)
            ((interval-equal? new-range increment) increment)
            ((empty-interval? new-range) the-contradiction)
            (else new-range))))
  interval? interval?)

Later, when the generic function "merge" is called, each handler is asked if it works on the operands.

As I understand multimethods, the dispatch function is defined across the set of implementations, with dispatch to a specific method based on the return value of the dispatch-fn. In the Scheme above, new assign-operation functions can define predicates arbitrarily.

What would be an equivalent, idiomatic construct in Clojure?

EDIT: The code above comes from "The Art of the Propagator", by Alexey Radul and Gerald Sussman.

Benefaction answered 1/10, 2011 at 18:53 Comment(2)
Inspired by Sussman's Strangeloop talk? Looks like a very cool paper. Should that read "generic function 'merge' is called"? Anyway, I think your best bet is a dispatch table stored in an atom. I don't think multimethods can help here. I'd love to hear how this turns out though.Socialminded
Yep, it should have read "merge". Thanks.Benefaction
R
3

You can do this with Clojure's multimethods fairly easily - the trick is to create a dispatch function that distinguishes between the different sets of predicates.

The easiest way to do this is probably just to maintain a vector of "composite predicates" that apply all of the individual predicates to the full argument list, and use the index of this vector as the dispatch value:

(def pred-list (ref []))

(defn dispatch-function [& args] 
  (loop [i 0]
    (cond
      (>= i (count @pred-list))     (throw (Error. "No matching function!"))
      (apply (@pred-list i) args)   i
      :else                         (recur (inc i)))))

(defmulti handler dispatch-function)

(defn assign-operation [function & preds]    
  (dosync
    (let [i (count @pred-list)]
      (alter pred-list conj   
             (fn [& args] (every? identity (map #(%1 %2) preds args))))
      (defmethod handler i [& args] (apply function args)))))  

Then you can create operations to handle whatever predicates you like as follows:

(assign-operation (fn [x] (/ x 2)) even?)

(assign-operation (fn [x] (+ x 1)) odd?)

(take 15 (iterate handler 77))
=> (77 78 39 40 20 10 5 6 3 4 2 1 2 1 2)
Rubstone answered 4/10, 2011 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.