Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Crafting Code in Clojure (tapestryjava.blogspot.se)
99 points by oskarth on Feb 1, 2013 | hide | past | favorite | 40 comments


Clojure is different from any other language I have ever used, and as I learned Clojure I had to struggle with the various programming habits that my mind has acquired over the last 14 years. Here were some of the main struggles:

1.) how do I live without a loop that lets me iterate some counters? The first Clojure (sort of) looping structure that I understood was (reduce), so for awhile I jumping through hoops to use (reduce) every time I wanted to go into a loop.

2.) how do I realize lazy sequences? I got myself badly stuck with some of the libraries, for instance Enlive, when I saved the nodes as a string and then later re-imported them to my code, at which point they were lazy sequences and I was wondering, how do I get this back to being Enlive nodes? Took me awhile.

3.) when do I divide code into a new file/namespace. In a language like Java the rule is something like "one class = one file" (I could qualify that, etc...). Clojure doesn't impose obvious dividing points on your code, and I think it takes a while to figure out what the right dividing points are (and I still wonder about what the best practice is, for instance: is it acceptable for code in one namespace to rely upon, and modify, an atom in a different namespace?)

Learning Clojure has been a fantastic educational experience for me. It is my favorite language and my favorite development eco-system. And I am lucky enough to be able to use at my job, so I get to work with it full time now, for which I am very grateful. (Previously I was getting a lot of corporate gigs where they needed PHP programmers who knew the Symfony framework -- I found those jobs very tedious, and, more to the point, after a few years I felt like I was not learning anything new).


Stick with it! I think that you will find Clojure + Compojure + Noir + Hiccup, etc. will be a refreshing break from PHP.

I was mostly using Ruby with Rails or Sinatra for a long time, and the switch to Clojure has been really good. Caveat: I have been actively using Lisp since 1982, and even so it took me a little while to switch over from Common Lisp. Keep at it, and I think Clojure will expand the way you think about programming.


Lisps are awesome! But to be fair, the Java code can be a lot more compact by using Guava:

    import com.google.common.base.Functions;
    import com.google.common.base.Joiner;
    import com.google.common.collect.FluentIterable;
    import com.google.common.collect.Ordering;
    import com.google.common.collect.Sets;
    import java.util.Map;
    import java.util.Collection;

    public class MapUtils {
        public static String sortedKeyList(Map<?, ?> map1, Map<?, ?> map2) {
            Collection<String> sortedKeys = FluentIterable
                    .from(Sets.union(map1.keySet(), map2.keySet()))
                    .transform(Functions.toStringFunction())
                    .toSortedSet(Ordering.natural());

            return sortedKeys.isEmpty()
                    ? "<none>"
                    : Joiner.on(", ").join(sortedKeys);
        }
    }
Not nearly as pretty as the Clojure code, but a lot nicer than without Guava.


This is a good point.

I personally find the "code compactness" to be a bit of a red herring. Java's greatest weakness, after all, is not the verbosity per se. If all of its other failings were addressed I would probably scarcely care about the amount of code it takes to perform a given task.

Likewise, compactness is not what lies behind the current FP trend. Compactness, after all, while related to programming paradigms is more directly tied to the strength and level of abstraction afforded by a given library / API. Ruby's standard library, for instance, allows for some pretty insane one liners.

This is to say nothing of the development time overhead involved in switching paradigms (non-trivial, IMHO). This alone offsets "compactness", both from the standpoint of up front dev time as well as readability / correctness, for (IMO) up to half a year.

As an aside, that code could be made yet more compact if written in Groovy (Groovy-as-a-Java-superset and not functional Groovy, necessarily).


> that code could be made yet more compact if written in Groovy

Indeed. It's very hard to read this and not think ... "but this would just be" in Groovy:

    [m1,m2]*.keySet().sum().sort().join(",")?:"<none>"
So small that I would rarely even make a separate function for it. I imagine it's much the same in many dynamic languages.


And in many static languages...


Not only dynamic languages, but also statically-typed languages that have type inference, such as Scala and the coming Java 8.


I like this better than the Clojure code.


I think that what's important is not that this particular example has a short Guava equivalent but that, overall, Clojure code seems to be way shorter than equivalent Java code (even if you use Typed Clojure).

A lot of people are talking about a 10x code size reduction, which is amazing.

Also, it's not as if the Clojure code was some obfuscated brainf*ck-style unreadable one-liners. It's very easy to read for anyone remotely familiar with Clojure.


Because this is turning into a fun language code-off, here's something in Python:

    ', '.join(sorted(str(x) for x in set(map1) | set(map2))) or '<none>'
This works by making sets of the keys of map1 and map2, taking the union of those sets, converting each key to a string, sorting those strings, and joining them with ', '. And then if that generated an empty string -- if both dicts were empty -- then it evaluates to '<none>'.

Am I missing some difficulty here?

EDIT: and for an arbitrary number of maps, this one works:

    import itertools
    ', '.join(sorted({str(x) for x in itertools.chain(*maps)})) or '<none>'
It iterates over all the keys of all the maps with itertools.chain(), converts everything to a string, adds everything to a set, sorts them, and then joins them with commas. I realize it's not a one-liner anymore because of the itertools import, but it's still pretty simple to follow.

I like Clojure, but for problems like this, it just doesn't seem to be doing quite as well as languages like Python.


I've never liked python code like this because I can't read it in left-to-right order nor right-to-left order. You have to read into the comprehension, then read left, then finally jump far right to the 'or'


It depends how you define better I suppose. I think they're both very readable, but I'm very willing to give up a little terseness for the benefits of a simpler syntax, immutable datastructures, and the kind of flexibility that lets you write things like -> to invert your control flow.


Nice article! I have one question for others though: Am I the only one who disagrees with ->> being easier to read?

From the OP:

  (->>
          (set (concat (keys map1) (keys map2)))
          (map str)
          sort)
And also from the OP:

  (sort (map str (set (concat (keys map1) (keys map2)))))
I'm so used to reading Lisp "in to out" that I have a difficult using the thread-fast macro. I guess it depends on your background. Do those who have have a bit of Lisp experience agree with me?


I'm used to read Lisp indented:

    (sort (map str
               (set (concat (keys map1)
                            (keys map2)))))


I'm a lisper and I prefer the macro. I have no idea if I'm in the minority or the majority. I have to admit that I've written more clojure than any other lisp, but I learned other lisps before I ever learned clojure.


I'm fairly new to Clojure, but am not new to bash (and its ilk), so tend to view the threading stuff as Clojure's version of bash's piping syntax (I realise they're not the same thing).


Common Lisp

    (defun example (&rest maps)
      (format nil "~:[<none>~;~:*~{~A~^, ~}~]"
              (sort (remove-duplicates
                     (loop for map in maps nconc
                           (loop for key being the hash-key of map collect key))
                     :test 'equal)
                    'string<)))


     def f[T](a:Map[String,T],b:Map[String,T]) = {
       val res= (a.keys++b.keys).toList.sorted.mkString(",")
       if (res.length == 0) None else Some(res)
     }
https://gist.github.com/4694911


The equivalent clojure code would be:

    (defn f [a b]
      (let [res (join "," (sort (distinct (mapcat keys [a b]))))]
        (if (empty? res) "<none>" res)))
Also I'm not a Scala expert, but I think you want .keySet instead of .keys, otherwise they won't be unique, right?


> you want .keySet instead of .keys, otherwise they won't be unique, right?

     scala> val p = Map(1->4,5->3)
     scala> p.keys
     res0: Iterable[Int] = Set(1, 5)
     scala> p.keySet
     res1: scala.collection.immutable.Set[Int] = Set(1, 5)
     scala> p.keys++p.keys
     res2: Iterable[Int] = Set(1, 5)
     scala> p.keySet++p.keySet
     res3: scala.collection.immutable.Set[Int] = Set(1, 5)


Golfing it for Haskell

    go :: (Show k, Eq k) => Map k v -> Map k v -> String
    go m1 m2 = map show >>> sort >>> intersperse ", " >>> mconcat >>> orNone
               $ keys m1 ++ keys m2
      where orNone [] = "<none>"
            orNone e  = e


Some quick tips. “intersperse ", " >>> mconcat” is “intercalate ", "”, and I would prefer “.” to “>>>”. Even though left-to-right composition reads better to me, right-to-left is the style.

Gratuitious pointfree version (imports omitted):

    go :: (Show k, Eq k) => Map k v -> Map k v -> String
    go = orNone . intercalate ", " . sort . map show .: (++) `on` keys
      where
      orNone [] = "<none>"
      orNone e = e
      f .: g = (f .) . g
      infixr 8 .:


Nice! I had (.) originally but reorganized it to compare better with the Clojure code. I really like (.:) and on though (which I'm always forgetting about).


That's missing a `nub` or something to get the unique keys. You can also combine the maps first and the results will come out sorted and unique already.... since we're golfing:

    go :: (Show k, Ord k) => Map k v -> Map k v -> String
    go m1 m2 = orNone . intercalate ", " . fmap show . keys $ m1 <> m2
      where orNone "" = "<none>"
            orNone x  = x


Bam! I didn't even think of taking advantage of the monoid.


It's great how he walks through the mental process of iterating his code, with examples at each step, and how it demonstrates the power of the language.


so this is horribly non-idiomatic ruby, but it was fun

  p ->(*_) {
    _.reduce({}, :merge).tap { |_| return "<none>" if _.empty? }.keys.uniq.map(&:to_s).sort.join(", ")
  }.({c:3,d:4,b:2}, {f:5,e:4,a:1})
  
  p ->(*_) {
    _.reduce({}, :merge).tap { |_| return "<none>" if _.empty? }.keys.uniq.map(&:to_s).sort.join(", ")
  }.({}, {})
  
  # "a, b, c, d, e, f"
  # "<none>"
edit: a little bit better.

  func = ->(*_) {
    return "<none>" if _.all?(&:empty?)
    _.flat_map(&:keys).uniq.map(&:to_s).sort.join(", ")
  }
  
  func.({c:3,d:4,b:2}, {f:5,e:4,a:1})
  #=> "a, b, c, d, e, f"
  func.({}, {})
  #=> "<none>"
https://gist.github.com/66b45e765a2aa6a97143


slightly more idiomatic, though perhaps not quite as clear as the clojure:

    def unique_keys(*hashes)
      result = {}
      hashes.each do |hash|
        result.merge! hash
      end
      return result.keys.map(&:to_s).sort.join(', ') unless result.empty?
      '<none>'
    end
https://gist.github.com/4695388


I think accumulating an initialized variable in an imperative #each loop is decidedly unidiomatic, just something we tend to do until we really embrace things like #map, #partition, #select, #reduce, etc. (And what a glorious day that is!)

I reckon what makes his first example somewhat unidiomatic is the arbitrary lambda and the use & inner return from the #taps block.

His updated example is just good ol tacit Ruby. But then again the amount of times I see an #each block with inner logic used instead of a simple point-free one-liner in Ruby really just proves your point.


well, I hate to beat a dead horse, but here's a one-liner (ruby):

  (map1.keys | map2.keys).map(&:to_s).sort.join(", ").instance_eval { empty? ? "<none>" : self }


My one-liner:

a = {"fred": 4, "wilma": 8} b = {"t-shirt": 6, "fred": 0}

print ", ".join(sorted(set(a).union(b))) or "<none>"


Since someone had to do it, here's a version in brainfuck:

https://github.com/fcostin/crafting_code_in_bf

Writing things in brainfuck is a good way to appreciate the things we sometimes take for granted, e.g. languages with more than 1 pointer.


Thanks for this post! I'm diving into clojure (currently a python dev), and I'm starting to get that tingly anticipation of being able to use these kinds of tools. It was really useful to see how you iterate through your solution.


That's funny--as a python guy I was squirming for a set comprehension immediately:

  >>> a = {'a':1,'b':2}
  >>> b = {'b':3,'c':4}
  >>> def keylist(*maps):
  ...     return ', '.join(sorted({str(k) for m in maps for k in m.keys()})) or '<none>'
  ...
  >>> keylist(a, b)
  'a, b, c'
  >>> keylist()
  '<none>'
I'm sure people will have differing opinions on the readability of a nested set comprehension, but from the bullets at the top of the article to one-liner implementation in ~30 seconds. (For an arbitrary number of maps, no less.)

(edit: added '<none>' on empty, fixed formatting, whoops now I'm over 30 seconds :))


Someone pointed out elsewhere in the thread that this kind of code has to be read from the inside out, and for that reason I think the clojure code makes a better balance of readable vs succinct.


This is pretty pedantic, but wouldn't

    for k in m
be more "pythonic" than

    for k in m.keys()

?


Since most other popular languages seem represented:

        string SortedKeyList<T, U>(params IDictionary<T, U>[] maps)
        {
            var keys = new SortedSet<string>(maps.SelectMany(m => m.Keys)
                                                 .Select(k => k.ToString()));
            return keys.Any() ? String.Join(", ", keys) : "<none>";
        }
EDIT: I just realized that a lot of curly brace languages look the same to the uninitiated. The above is C#.


A Ruby version that attempts to be as close to the elegant and readable Clojure solution as possible via similar 'crafting':

    def sorted_key_list(maps)
      maps.map(&:keys)
          .reduce(:&)
          .sort
          .tap { |a| a.push "<none>" if a.empty? }
          .join ","
    end
Works out at 24 syntax-related punctuation symbols for Ruby, 20 for Clojure (ignoring the replace-empty function, admittedly).


You actually want ".reduce(:|)", & does intersection on arrays. You can replace that .tap block with gsub abuse for a bit more golfing, and can replace the map/reduce combo with flat_map/uniq to eliminate one symbol-to-proc:

    def sorted_key_list(maps)
      maps.flat_map(&:keys)
      .uniq
      .sort
      .join(",").gsub /^$/, "<none>"
    end


D'oh, I misread the spec; this is where understanding the Clojure properly may have helped too. I thought the keys had to be in both maps, not the keys from both maps. Good call on the | and flat_map then :-)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: