(defstruct color :red :green :blue)
(defn red [v] (struct color v 0 0))
(defn green [v] (struct color 0 v 0))
(defn blue [v] (struct color 0 0 v))
(defn keys-with-value-matching [map test-fn]
(for [pair (map identity map) :when (test-fn (last pair))]
(first pair)))
(defn basic-colors-in [color]
(keys-with-value-matching color (comp not zero?)))
(defmulti color-string basic-colors-in)
(defmethod color-string [:red] [color] (str "Red: " (:red color)))
(defmethod color-string [:green] [color] (str "Green: " (:green color)))
(defmethod color-string [:blue] [color] (str "Blue: " (:blue color)))
(defmethod color-string :default [color]
(str "Red: " (:red color) ", Green: " (:green color) ", Blue:
" (:blue color)))
I am reasonably happy with the struct and the defmulti, but I am sure
there is a more elegant way to do basic-colors-in and especially keys-
with-value-matching. What is a more idiomatic approach?
Thanks,
Stuart
[1] http://blog.thinkrelevance.com/2008/8/4/java-next-common-ground
[2] http://blog.thinkrelevance.com/2008/8/12/java-next-2-java-interop
[3] http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-4
I'm not sure why you used "(map identity map)" instead of just "map",
but other than that I think the only major idiom you're missing is
destructuring, which tidies this up a bit:
(defn basic-colors-in [color]
(for [[k v] color :when (not= v 0)] k))
Maybe others can spot more simplifications.
> (defmulti color-string basic-colors-in)
I don't feel qualified to comment on this use of multimethods, or
multimethods at all really. I simply wouldn't have thought of using
them in this case, but maybe it's fine. Maybe it's even great, I
don't know. :-)
--Chouser
Sorry to reply to myself, but immediately after sending I realized
(map identity map) isn't doing what I had thought it was, and since
others may be similarly confused...
You've already shadowed the builtin clojure/map function with your
function argument named map. So inside that function, it doesn't
matter whether you use the word "map" in the initial position or in
the last position -- both uses of the word "map" in (map identity map)
refer to your argument. In order to get to the builtin function you'd
have to say "clojure/map".
When I first read your code I assumed, probably partially because of
the use of "identity", that that expression was calling clojure/map.
But since it's not, how is it working? Let's say the hash-map {:foo
:bar} is passed in for "map". That means the expression in question
is the same as ({:foo :bar} identity {:foo :bar}). A hash-map in the
function position like that looks up it's first parameter ("identity"
in this case) in itself. Chances are the identity function wasn't
used as a key the hash-map, so the lookup will fail. Therefore the
lookup falls back to the default expression given in the next
argument, which in this case is the hash-map itself {:foo :bar}. And
thus we get the return value we were expecting, but for entirely
different reasons.
This also means there's a subtle bug lurking in the code. Not likely
to be a problem, I admit, but it would nonetheless be surprising:
user=> (color-string (assoc (struct color 20 30 10) identity {:red 99}))
"Red: 20"
By supplying a color struct that has an "identity" key, color-string
uses the key list from identity's value (that is, just :red), but the
value number from the original struct.
I don't know if that made any sense to anyone, but anyway...
--Chouser
Chouser: That is way better, thanks!
Stuart
You're stealing my thunder. Using a type tag would be more similar,
but would introduce what I consider to be a bug in the Scala version:
(red 10) is not the same as (struct color 10 0 0).
> A critical difference, IMO, is that pattern matches are closed, and
> multimethods are open:
With Scala extractors, pattern matches can be open too. That said, I
find that writing the extractors to be syntactic vinegar compared to
multimethods. But Scala is a big beast, and I won't be surprised if I
find some tricks that make writing the extractors easier.
Stuart
Adding explicit smiley face: :-)
>>> A critical difference, IMO, is that pattern matches are closed, and
>>> multimethods are open:
>>
>> With Scala extractors, pattern matches can be open too.
>
> Interesting. By that do you mean an existing match can work with
> subsequently-defined types or that new 'cases' can effectively be
> added later? Do you have a pointer to an example of the latter?
The former is easy. It's not obvious to me how to do the latter, but
my instinct is that it could be done with some extractor trickery. If
I come up with an example I will post it.
Stuart