54 comments

[ 3.2 ms ] story [ 108 ms ] thread
Clojure is beautiful and elegant. If not for it's lack of popularity and necessity of keeping up with JS constantly, it would be my most used language especially on side projects.

Clojure is one of my favorite languages. If they get the stack traces/better errors figured out, it will be damn near perfect IMO.

The stacktraces are a bit verbose but still very readable
Keeping up with JS? Maybe you're referring to clojurescript, for that criticism?
I think he meant he has to spend so much time keeping up with JS, that he does not have the chance to use Clojure more.
The Clojure code is shorter, but that's almost entirely because it's dynamically typed. And that's not necessarily a benefit, either. Cheshire just takes JSON and turns it into plain old Clojure data (maps, vectors, strings or keywords, etc.) whereas Aeson requires the programmer to manually unroll the whole structure from top to bottom into user-defined types. But by doing so it guarantees that if something is missing or unexpected, the code fails early and fails fast. Plus the static typing enables compile-time checking of all your code which uses it, rather than how Clojure accesses JSON which is so often stringly-typed because that's easiest/shortest/fastest.
hmm still unsure what the types win me then
It's pretty handy to only have to be vigilant at the boundary layer. With the untyped approach, all consumers of the deserialized map have to guard against data which doesn't conform to expectations. With the typed approach, all subsequent functions only have to work with data conforming to the specification as laid out by types.
If your only benchmark is less code, dynamic typing is fine. This example also doesn't really exemplify the value of static typing, in that the example is a "sandboxed"(it does not have to work with external APIs, other developers, etc) "non-maintained"(once it's written it won't change over a few years requiring support) code block.

An example of "Lets take 6 devs and have them build something" would probably create a more obvious use for Haskell over Clojure, as the biggest problem (to me) with Dynamic Typing is its scalability under development. As in, alone I (and most devs) could write dynamically typed code with little issue, as all decisions are made internally and guard cases can be written correctly based on those internal decisions. But throwing a second dev (or n devs) into the mix makes those internal decisions much more difficult, making the guard clauses much more difficult to write correctly. Static typing takes a lot of the guard-clause-writing out of the picture, as the compiler creates them for you.

It completely eliminates a class of runtime errors from your code, while also allowing all code that consumes the data to make assumptions about the data.

In the untyped code you must validate everywhere you access the data or trust that someone above you already validated it (which can break silently with any future modification).

Aeson/Haskell doesn't require decoding json into user-defined data types. It supports the scenario of turning the data into a plain Map (clojure style) as well. The plain Map style is simply a less preferred paradigm in haskell, but its very easy to support this. Getting a map out from your json is a one liner.
If you just want to get shit done, Clojure is fine (though any other Lisp would arguably be better than Clojure).

If you want to get shit done, and make sure the types are correct, and make sure all side effects are properly managed and accounted for, that's Haskell's niche.

> ... Clojure is fine (though any other Lisp would arguably be better than Clojure).

Care to elaborate why you think any other Lisp is better than Clojure? I would like to hear your arguments.

Not the GP, but for quick things I prefer CL because:

* TIMTOWTDI. Doing everything with map/reduce/filter is great, but sometimes it's more direct to just use the LOOP macro. Immutability is great, but sometimes it's way faster to just SETF something (and not have to worry about atoms).

* Batteries (more) included. IME, I find myself needing to use third-party libraries sooner with Clojure than with CL. Also, QuickLisp is arguably faster to get rolling with than Leiningen (I've found it easier to get the new library into my existing lisp image). And CL tends to have clusters of functions that do similar-but-different things, whereas Clojure puts the cognitive burden on you to do things right (example off the top of my head: CL has REMOVE, REMOVE-IF, and REMOVE-IF-NOT, Clojure has ????? (like ten different ways, I'm serious), `(filter pred list)`, and `(filter (complement pred) list)`).

* The debugging situation is fantastic. Both because CL deals with errors a lot more gracefully than Clojure (are we allowed to blame the JVM?) and because SLIME >>> CIDER, at least for now.

I haven't used the LOOP macro in CL, but the for macro in Clojure is also very powerful and (imho) very readable.

My take on the 'versify' example in 4 lines:

    (for [book book-order
          chapter (range 1 1000) :when (get-in bible [book (str chapter)])
          verse (range 1 1000) :when (get-in bible [book (str chapter) (str verse)])]
      [book chapter verse (get-in bible [book (str chapter) (str verse)])])
Notice that a single "for" can iterate in a multi-level structure. You can also use :let if you don't want to call (get-in) multiple times (which makes the code shorter, but more redundant and less efficient)
I use CL/Scheme/Clojure at work. I generally prefer comprehension style ('for' in Clojure, srfi-42 in Scheme) but sometimes CL's loop let me save a few nestings.

One of such patterns is when I have to accumulate multiple kind of things while I zip through the input. Somewhat contrived example: You have a hashtable that maps integer key to a list of strings. You want to scan it just once and build two lists, strings associated with odd keys and strings associated with even keys.

    ;; populate input 
    (defvar input (make-hash-table :test 'eql))
    (setf (gethash 1 input) '("ichi" "hi"))
    (setf (gethash 2 input) '("ni" "fu"))
    (setf (gethash 3 input) '("san" "mi"))
    (setf (gethash 4 input) '("yon" "shi" "yo"))
    (setf (gethash 5 input) '("go" "itsu"))

    ;; loop
    (loop for k being each hash-key in input
       when (oddp k) append (gethash k input) into odds 
       when (evenp k) append (gethash k input) into evens 
       finally (return (values odds evens)))
    ; => ("go" "itsu" "san" "mi" "ichi" "hi") and ("yon" "shi" "yo" "ni" "fu")
You surely know this but for other people interested:

1. You could add "using (hash-value v)" in the iteration clause to directly have the value (no gethash).

2. There is maphash too.

Right! I tried to construct a terse example in hurry but apparently missed the mark. Usually loop comes handy when conditions and the way to extract values gets more complicated.
How is it more direct to use loop? As someone who has been writing production clojure for nearly 5 years I usually only see loop macro used by devs coming from oop because that's a more comfortable pattern for them. I have rarely needed to use it.

As far as collections go, there's filter, remove, reduce, and various take functions. I don't see the deficiency.

Debugging is still an issue, but I personally don't find it too difficult... probably because I'm used to it.

I believe he is referring to Common Lisp's loop, not Clojure's loop.
Maybe there's a better example, but REMOVE-IF sounds like Clojure's 'remove', REMOVE-IF-NOT sounds like 'filter', and REMOVE sounds like 'disj', or possibly '(remove #{item} list)'.
Clojure has neither the cool constructs of CL (CLOS, conditions) nor the minimal design and fantastic set of control primitives of Scheme. In fact it has no significant advantage over Scheme aside from running on the JVM, and a few disadvantages (recur, why TF are lambda lists vectors?, etc.). It's different enough to be weird and annoying, but the differences aren't advantageous enough to make me want to switch: Scheme (and CL!) has been made to run efficiently on the JVM (with some caveats, which Clojure has also!).
I mostly agree with you, but I think the out-of-the-box immutable data structures and sugar around around maps are very nice compared to the out-of-the-box scheme/racket experience.
I'm a fan of the out of the box immutable data structures and like the sugar, though you can get both in Lisp if you want. (Presumably Racket as well.) Lisp has everything, and more, though sometimes you have to work for it. I do enjoy Clojure's interop with the JVM, but that along with the community producing interesting things (this includes ClojureScript) are the primary reasons I care about Clojure at all. On the nice-to-work-with side of things I really enjoy that it was designed by one person rather than a standards committee from decades ago, it feels fresh working with it, but if you stranded me on an island and gave me one language to have for the rest of time I'd pick Lisp for its power.
> you can get both in Lisp if you want

The whole point of benefiting from immutable data structures is their ubiquity. If they're something you reach for only when you personally decide you need them, they're unlikely to work well with 3rd-party code; you will have to do a lot more digging to understand how the code you're writing will behave in a concurrent context vs having it be obvious. Making it opt-in is almost making it pointless, unless you never use 3rd-party code.

Is it possible to make truly persistent data structures in Lisp, rather than just persistent by convention? Here are some benefits that you can only get if the language implementation knows your data structures are immutable:

(0) The compiler can automatically perform optimizations like fusing small nodes and hash consing.

(1) The garbage collector can take advantage of the fact that traversing (the immutable portion of) the heap sequentially produces a topological sorting of the object graph.

recur

I actually really like recur because it signifies intent. It tells the future-me that I intended for this function to be tail recursive (ie, it not being tail-recursive should be considered an error).

It does, but it's still too weak, e.g. it only works if the function is calling itself.
Like my sibling commenter, I would like to know what’s wrong with clojure when compared to other LISPs
>If you want to get shit done, and make sure the types are correct, and make sure all side effects are properly managed and accounted for, that's Haskell's niche.

Dont Clojure's immutable types handle the side-effect issues well? Without the contortions that Haskell puts you through.

Clojure's immutable types mean just that—the values are immutable (e.g., in CL you could do (defvar x 42) (setf x 84), but in Clojure the equivalent is not allowed).

But there are many more side effects than changing variables. Clojure makes no guarantees that a given function is referentially transparent, nor does it promise that a given function doesn't write to stdout/stderr, read from stdin, change the file system, make a network connection, send missiles to foreign countries, etcetera.

Not arguing that the following method can be trusted as much as in Haskell but properly used also prevents this kind of scenario:

https://clojuredocs.org/clojure.core/io℅21

The 'io!' macro marks a code block to have side effects which prevents using it in a STM transaction as 'dosync' might need to execute the functions multiple times in case of conflicts during the optimistic locking.

> properly used

In the sense C programmers use the term? Why should humans be trusted with identifying all operations that can't be safely performed within a transaction?

(comment deleted)
(comment deleted)
Mirror of the github link please? I'm FW'ed...
That haskell implementation is less than ideal. If he uses Data.Aeson.Lens, most of that instance declaration business is not necessary. It would just be mapping over the chapter keys to create the list of lists, then sorting it.
This seems like a contrived task constructed to be convenient for Clojure.
I'm reasonably certain I've written JSON-parsing logic in Haskell that's substantially smaller than this.
I know barely enough haskell, passively, to dislike quite a lot of this code (like really, 3 lines on 3 places, just to case on a maybe to throw an error? w/a just calling fromMaybe or even fromJust? and manual instances??) - but, could someone more experienced suggest a saner one?

    flatten :: ByteString -> [ (String, Int, Int, String) ]
    flatten json = case decode json of
      Nothing -> []
        -- Type inference here can infer that we're using `val` as
        -- :: HashMap String (HashMap String (HashMap String String))
      Just val ->
        let books = ["Genesis", "Exodus" {- ...and so forth -} ]
        in [ (book, chNum, vsNum, verse)
              -- for every book in the list of books...
           | book <- books
               -- find the corresponding map
           , Just bookMap <- [HM.lookup book val]
               -- for every number in the range [1..max] for chapters
           , chNum <- [1..HM.size bookMap]
               -- pull that map out of the submap
           , Just chMap <- [HM.lookup (show chNum) bookMap]
               -- ...and for every number in the range [1..max] for verses
           , vsNum <- [1..HM.size chMap]
               -- ...pull the verse out of the sub-sub map
           , Just verse <- [HM.lookup (show vsNum) chMap]
           ]
Okay, this isn't as type-safe or signed-in-triplicate as the supplied Haskell solution, but if we're comparing size-to-size, then this is pretty terse while also avoiding partial functions (the supplied Haskell solution uses read, which can throw an error if we happen to have a non-numeric key in the wrong place) and still working within the restrictions of what was given.
One thing true of all bugs, it passed the type checker.
That Haskell example needs some love. There should also be a Haskell lens example. Perhaps I can help out when I get home.
scala?

  #!/usr/bin/env amm
  
  import $ivy.`io.circe::circe-core:0.5.1`
  import $ivy.`io.circe::circe-generic:0.5.1`
  import $ivy.`io.circe::circe-parser:0.5.1`
  
  import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
  
  val bookOrder = Array("Genesis", "Exodus", /* and so forth */).zipWithIndex.toMap
  
  type Bible = Map[String, Map[Int, Map[Int, String]]]

  val bible = /* load the json file */

  decode[Bible](bible).map { parse =>
    (
      for {
        (bookName, book) <- parse
        (chapterNo, chapter) <- book
        (verseNo, verse) <- chapter
      } yield (bookName, chapterNo, verseNo, verse)
    )
      .toList
      .sortBy { b =>
        (bookOrder.get(b._1), b._2, b._3, b._4)
      }
  }.foreach(_.foreach(println))
Well, here's a partial JavaScript solution, which doesn't use new ES2015 features and doesn't actually implement the sort. Feel free to riff:

   http://jsbin.com/vikesu/edit?html,js,console
Obviously, Scala is better than either of those languages.

    #!/usr/bin/env amm
    import $ivy.`com.typesafe.play::play-json:2.5.8`
    import java.nio.file._, play.api.libs.json._

    val bookOrder = Seq(
        "Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
        "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
        "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
        "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
        "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
        "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
        "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
        "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
        "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
        "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
        "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"
      ).zipWithIndex.toMap

    val bible = Json.parse(Files.readAllBytes(Paths.get("ESV.json")))
      .as[Map[String,Map[String,Map[String,String]]]]

    val flattened = for((book, x) <- bible; (chapter, x) <- x; (verse, x) <- x)
      yield (book, chapter.toInt, verse.toInt, x)
    val result = flattened.toSeq.sortBy { case (b, c, v, _) => (bookOrder(b), c, v) }
    println(result)
---

Also, Python

    #!/usr/bin/env python3
    import json

    books = [
        "Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
        "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
        "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
        "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
        "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
        "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
        "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
        "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
        "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
        "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
        "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"
    ]
    books = {book:i for i, book in enumerate(books)}

    with open('ESV.json') as f:
        result = sorted(
            ((b, int(c), int(v), x) for b, x in json.load(f).items() for c, x in x.items() for v, x in x.items()),
            key=lambda part: (books[part[0]], part[1:])
        )...
Code golf? Code golf!

    let o = [|"Genesis"; "Exodus"; "Leviticus"; "Numbers"; "Deuteronomy";
              "Joshua"; "Judges"; "Ruth"; "1 Samuel"; "2 Samuel"; "1 Kings"; "2 Kings";
              "1 Chronicles"; "2 Chronicles"; "Ezra"; "Nehemiah"; "Esther"; "Job";
              "Psalms"; "Proverbs"; "Ecclesiastes"; "Song of Solomon"; "Isaiah";
              "Jeremiah"; "Lamentations"; "Ezekiel"; "Daniel"; "Hosea"; "Joel"; "Amos";
              "Obadiah"; "Jonah"; "Micah"; "Nahum"; "Habakkuk"; "Zephaniah"; "Haggai";
              "Zechariah"; "Malachi"; "Matthew"; "Mark"; "Luke"; "John"; "Acts";
              "Romans"; "1 Corinthians"; "2 Corinthians"; "Galatians"; "Ephesians";
              "Philippians"; "Colossians"; "1 Thessalonians"; "2 Thessalonians";
              "1 Timothy"; "2 Timothy"; "Titus"; "Philemon"; "Hebrews"; "James";
              "1 Peter"; "2 Peter"; "1 John"; "2 John"; "3 John"; "Jude"; "Revelation"|]

    let a s = Map.toSeq >> Seq.sortBy s >> Seq.toArray

    JsonConvert.DeserializeObject<_> BIBLE_TEXT    
    |> Map.map (fun _ -> Map.map (fun _ -> a fst) >> a fst)
    |> a (fun (n, _) -> Array.findIndex ((=) n) o)
    |> printfn "%A"
F#, right?
Yup.

'Minified', for fun:

    let a s=Map.toSeq>>Seq.sortBy s>>Seq.toArray
    let m f=Map.map(fun _->f)  
    printfn"%A"(a(fun(n, _)->Array.findIndex((=)n)o)(m(m(a fst)>>a fst)(JsonConvert.DeserializeObject<_>BIBLE_TEXT)))
In F# you'd normally use a type provider for this kind of work, but for the sake of exercise:

    type Chapter = Map<int, string>
    type Book    = Map<int, Chapter>
    type Bible   = Map<string, Book>
    
    let bookOrder = [|"Genesis"; "Exodus"; "Leviticus"; (* etc.. *) |]

    let sortChapter = Map.toSeq >> Seq.sortBy fst
                      >> Seq.toArray

    let sortBook = Map.map (fun _ -> sortChapter)
                   >> Map.toSeq >> Seq.sortBy fst
                   >> Seq.toArray

    let sortBible = Map.map (fun _ -> sortBook)
                    >> Map.toSeq >> Seq.sortBy (fun (bookname, _)-> Array.findIndex ((=) bookname) bookOrder)
                    >> Seq.toArray

    BIBLE_TEXT
    |> JsonConvert.DeserializeObject<Bible>
    |> sortBible
    |> printfn "%A"
FP 101 suggests you could extract the Map.toSeq >> Seq.sortby x >> Seq.toArray function, but I don't think it gains any readability, and in any case it's just an artifact of the problem requiring an array output even though a Map would make more sense.