Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way to pass a method name for evaluation in Clojure?

I'm passing the name of a function for use in another method.

(defn mapper [m function]
  (cond
   (= '() m) '()
   true (cons (function (first m))
            (mapper (rest m) function))))

(println (mapper '((blue red)(green red)(white red)) #'first))

Is there a more idiomatic way to do this in clojure?

like image 480
hawkeye Avatar asked Aug 11 '10 11:08

hawkeye


2 Answers

  • Prefer vectors to lists. You don't have to quote a vector most of the time, and it has better performance for a lot of things, like random access. Lists are used much more rarely in Clojure than in other Lisps.
  • Prefer keywords to quoted symbols. Keywords stand out as "constant strings" or enumerated values. Keywords in Clojure can belong to a namespace, so they have all the advantages of symbols. And again, there's no need to quote keywords, which is nice. Quoted symbols are used pretty rarely in Clojure, unless you're writing macros.
  • #'first is the var called "first"; first is the value of the var called "first", i.e. the fn. In this case (#'first foo) and (first foo) give the same answer, but #'first does an extra dereference every time you call it. So don't do this unless you want that dereference to happen over and over. There's usually no need to use #'.
  • The built-in map is lazy, whereas yours isn't. The built-in map takes advantage of chunked seqs for better performance, whereas yours doesn't. Idiomatic code doesn't have to be lazy or use chunked seqs, but keep in mind that the builtins have some of this magic going on. So it's good to take advantage.
  • Rather than (= '() x), the idiomatic test for an empty seq is (seq x), which returns nil if x is empty. Note that in Clojure, (= '() nil) is false.
  • If you do ever need to use the empty list (which you should rarely need to do), you don't have to quote it. Just use ().
  • Built-in map takes the function argument first because it accepts multiple collection arguments. When a function takes multiple arguments, those arguments have to go last in the argument list. I think it reads better the other way too: "(map f coll): map this function across this collection".
  • There's no need to use cond if you only have two options. You can use if instead. And if one of the branches in your if returns nil, you can use when. It's nice to use when and if when appropriate, because they signal your intentions to the reader immediately, whereas cond could do anything and forces the reader to read more.

Rafał Dowgird's version is idiomatic, except I'd flip the order of arguments around. And I'd call it like this:

user> (mapper first [[:blue :red] [:green :red] [:white :red]])
(:blue :green :white)
like image 90
Brian Carper Avatar answered Sep 28 '22 00:09

Brian Carper


I believe you got it mostly idiomatic. Clojure's own map uses:

(defn mapper [coll f]
 (when-let [s (seq coll)]
    (cons (f (first s)) (mapper (rest s) f))))

I have shortened it severely - the original produces a lazy sequence, deals with multiple collections, chunked-seqs, etc. By the way - I assume you want to pass the actual function, not it's name.

The coll and f are idiomatic arg names to represent collections and functions, respectively.

like image 34
Rafał Dowgird Avatar answered Sep 27 '22 23:09

Rafał Dowgird