On Nov 19, 7:45 am, Stuart Halloway <[EMAIL PROTECTED]> wrote:
> Hi all,
>
> I am working on the multimethod chapter this week. This has required a
> lot of exploration, as the multimethod feature set goes well beyond
> what most people are using yet. I have hit one rough spot: derive. I
> have working code (below), but I don't like the way I have to call it
> with fully qualified keywords, e.g.
>
> (service-charge {:tag :examples.multimethods.service-charge-3/
> checking :balance 100})
> -> 25
> (service-charge {:tag :examples.multimethods.service-charge-3/
> checking :balance 10000})
> -> 0
>
> I feel that I have made a wrong turn somewhere. Here are my assumptions:
>
> 1. I (the implementer) have to write my dispatch functions with
> qualified names, if I want to use derive.

Derive works only with qualified names for an important reason - so it
can be extensible without clashes. But it's important to logically
segregate hierarchical things from non, as namespace control is
orthogonal.

>
> 2. John Doe (the caller) must use fully qualified names *everywhere*.
> Since he does not live in my namespace he cannot use the ::.
>

Qualified names, yes, fully, no. In particular, :: supports aliases,
as shown below, or ` can be used with symbols.

> It's the latter that bothers me. It seems so ugly that I would never
> use hierarchical names for anything, which makes me think I am missing
> something. To make matters worse:
>
> 3. Once I use :: once on any keyword in my implementation, it is a
> quick slope to using it other places too, just so I don't have to
> remember which ones I chose to qualify and which ones I didn't. In the
> code below, :premium and :basic become ::premium and ::basic just for
> consistency with ::checking and ::savings.

Keywords are going to be used in different contexts for different
reasons, saying they should all use :: for consistency is sort of
punting on making decisions about their use.

>
> ---------------------------------------------------------------------------
> (ns examples.multimethods.service-charge-3)
>
> (defmulti account-level :tag)
> (println ::checking)
> (defmethod account-level ::checking [acct]
>    (if (>= (:balance acct) 5000) ::premium ::basic))
> (defmethod account-level ::savings [acct]
>    (if (>= (:balance acct) 1000) ::premium ::basic))
>
> (derive ::savings ::account)
> (derive ::checking ::account)
>
> (defmulti service-charge (fn [acct] [(account-level acct) (:tag acct)]))
> (defmethod service-charge [::basic ::checking]   [_] 25)
> (defmethod service-charge [::basic ::savings]    [_] 10)
> (defmethod service-charge [::premium ::account] [_] 0)

Here are two ways to do it that I would consider idiomatic, or at
least as intended. In both implementations, account tags are
hierarchical, and account levels are considered an enumeration,
account 'types' are capitalized. Consumer code uses normal namespace
management to deal with the names.

In the keyword version, note how you can use ::alias/name and get a
resolved keyword. In the symbol version note the consistent use of
syntax-quote, and the ability to pull in names using use/refer.

Other than the default resolution (symbols are resolved and keywords
aren't), they pretty much have parity here. In particular, note
symbols are functions of maps like keywords, a feature not used below
but important for their use as keys in maps.

;;;;;;;;;;;;;; using keywords ;;;;;;;;;;;;;;;;;;;;;;

(ns examples.multimethods.service-charge-3)

(defmulti account-level :tag)

(defmethod account-level ::Checking [acct]
   (if (>= (:balance acct) 5000) :premium :basic))
(defmethod account-level ::Savings [acct]
   (if (>= (:balance acct) 1000) :premium :basic))

(derive ::Savings ::Account)
(derive ::Checking ::Account)

(defmulti service-charge (fn [acct] [(account-level acct) (:tag
acct)]))
(defmethod service-charge [:basic ::Checking]   [_] 25)
(defmethod service-charge [:basic ::Savings]    [_] 10)
(defmethod service-charge [:premium ::Account] [_] 0)

(in-ns 'user)
(alias 'sc 'examples.multimethods.service-charge-3)

(sc/service-charge {:tag ::sc/Checking :balance 100})
(sc/service-charge {:tag ::sc/Checking :balance 10000})


;;;;;;;;;;;;;; using symbols ;;;;;;;;;;;;;;;;;;;;;;

(ns examples.multimethods.service-charge-4)

(defmulti account-level :tag)
(defmethod account-level `Checking [acct]
   (if (>= (:balance acct) 5000) :premium :basic))
(defmethod account-level `Savings [acct]
   (if (>= (:balance acct) 1000) :premium :basic))

(declare Account Savings Checking)

(derive `Savings `Account)
(derive `Checking `Account)

(defmulti service-charge (fn [acct] [(account-level acct) (:tag
acct)]))
(defmethod service-charge [:basic `Checking]   [_] 25)
(defmethod service-charge [:basic `Savings]    [_] 10)
(defmethod service-charge [:premium `Account] [_] 0)


(in-ns 'user)
(refer 'examples.multimethods.service-charge-4)

(service-charge {:tag `Checking :balance 100})
(service-charge {:tag `Checking :balance 10000})

Hope that helps,

Rich


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to