
Dueling Rhetoric of Clojure and Haskell - dukerutledge
http://tech.frontrowed.com/2017/11/01/rhetoric-of-clojure-and-haskell/
======
bpolverini
The dueling rhetoric is the same rhetoric that has been around for decades:
Some people really feel type systems add value; others, feel it's a ball and
chain. So which is it? The answer is probably "yes." We should all believe by
now since history has proven this correct. Most of the time you start with no
type system for speed. Then you start adding weird checks and hacks (here's
lookin' at you clojure.spec). Then you rewrite with a type system.

I'm a devout Clojure developer. I think it delivers on the promises he
outlines in his talk, but I also have no small appreciation for Haskell as an
outrageously powerful language. Everyone robs from Haskell for their new shiny
language, as they should. Unfortunately, not a night goes by where I don't ask
God to make me smart enough to understand how a statement like "a monad is
just a monoid in the category of endofunctors" can radically change how I
implement marginably scalable applications that serve up JSON over REST.
Clojure talks to me as if I were a child.

Rich Hickey is selling the case for Clojure, like any person who wants his or
her language used should do. His arguments are mostly rational, but also a
question of taste, which I feel is admitted. As for this writer, I'm glad he
ends it by saying it isn't a flame war. If I had to go to war alongside
another group of devs, it would almost certainly be Haskell devs.

~~~
kreetx
The fact that Haskell is smarter than me is exactly why I have been keeping at
it!

There is no fun left if you know all the overarching principles of a language,
and you realize it still doesn't solve your problem. This happened to me when
learning Python, this is also why I don't really look at Go or Rust. They're
good languages, I might use them at a workplace someday, but you can get to
the end of their semantics, but be still left with the feeling that it's not
enough.

(EDIT: minor wording improvement)

~~~
marcosdumay
I love how I just keep learning Haskell, and keep improving, despite how much
I already did it.

That said, Python is also smarter than me. The possibilities with monkey
patching and duck typing are endless. But differently from Haskell, Python is
not a good teacher, so I tend to only create messes when I go out of the way
exploring them.

~~~
jstimpfle
> That said, Python is also smarter than me. The possibilities with monkey
> patching and duck typing are endless.

Don't do it, 99.9% of the time. It's that simple.

There is seldom a reason to use more than just defs - and a little syntactic
sugar (like list comprehensions) just to keep it readable.

Even the use of classes is typically bad idea (if I do say so). Just because:
there is no advantage to using it, except when everything is super dynamic.
And if that's the case, I suggest that's a smell, indicating that one is
trying to do to many things at once, not properly knowing the data.

Nevertheless using classes (or any other superfluous feature) makes everything
more complicated (less consistent - need new ways to structure the program,
new criterions whether to go for a class or not to or where to bolt methods
on,...).

Don't use "mechanisms" like monkey patching just because they exist. They are
actually not mechanisms - just curiosities arising from an implementation
detail. The original goal is simplicity: Make everything be represented as a
dict (in the case of python)

> The possibilities with monkey patching and duck typing are endless.

I think there are many more "obvious" ways to do things in Haskell than in
Python just because you as a developer need to draw the line between static
and dynamic. And if you later notice that you chose the line wrong, you have
to rewrite everything.

In Python - or any other simple language - there is typically one obvious way
to do things. At least to me.

~~~
Xephyrous
Classes definitely give you a lot of rope to hang yourself with (metaclasses,
inheritance, MULTIPLE inheritance), but they have their place. I'll usually
start with a function, but when it gets too big, you need to split it up.
Sometimes helper functions is enough, but sometimes you have a lot of state
that you need to keep track of. If the options are passing around a __kwargs
dictionary, and storing all that state on the class, I know which I 'd pick.

You can memoize methods to the instance to get lazy evaluation, properties can
be explicitly defined up-front, and the fact that everything is namespaced is
nice. You can also make judicious use of @staticmethod to write functional
code whenever possible.

~~~
jstimpfle
You can always opt for explicit dict passing. You are right that it's more
typing work (and one can get it wrong...), but the resulting complexity is
constant in the sense that it is obvious upfront, never growing, not dependent
on other factors like number of dependencies etc.

When opting for explicit, complexity is not hidden and functions are not
needlessly coupled to actual data. Personally I'm much more productive this
way. Also because it makes me think through properly so I usually end up not
needing a dict at all.

Regarding namespacing, python modules act as namespaces already. Also manual
namespacing (namespacename+underscore) is not that bad, and technically avoids
an indirection. I'm really a C programmer, and there I have to prefix manually
and that's not a problem.

------
wellpast
> Much of the rhetoric that is currently flying around is a false dichotomy.

The author here is missing the rhetoric. The rhetoric is not about the
programming language but about how we should be doing information processing.
Except that the author _isn 't_ missing that point:

> In Haskell we typically “concrete” data with record types, but we don’t have
> to.

Great. That _is_ the dichotomy. And it's not a "false" one. This is the
question: should we be "concreting"? That's the whole dichotomy/point that is
being made. By encoding EDN/Clojure in Haskell the author has gone through a
cute intellectual puzzle but hasn't contributed to the crux of the discussion.
(Indeed, he's tried to dismiss it as "false".)

The ergonomics that he ends up with are fairly lean (at least in the examples
he's shown), though the Clojure expressions are a little leaner. But that's
probably because Clojure has actually taken a stance/belief/opinion on the
very real question/dichotomy at hand.

~~~
GreaterFool
It's a little bit more than just a cute intellectual puzzle. One could build
an efficient EDN library based on that or very similar type. See Haskell's
JSON library:

[https://github.com/bos/aeson/blob/master/Data/Aeson/Types/In...](https://github.com/bos/aeson/blob/master/Data/Aeson/Types/Internal.hs#L342)

> though the Clojure expressions are a little leaner

Yes they are. The price is complete lack of type safety. And the benefit is an
insignificantly small reduction in boilerplate code.

The number of bugs I've seen where somebody would "get" a number that turned
out to be a string or string that turned out to be a number...

~~~
dustingetz
EDN is not JSON. EDN is Extensible. OP has everyone in this thread arguing
about a strawman.

~~~
tome
Then I think you're really going to need to educate us about what EDN really
is ...

~~~
escherize
Educate yourselves. [https://github.com/edn-
format/edn/blob/master/README.md#tagg...](https://github.com/edn-
format/edn/blob/master/README.md#tagged-elements)

~~~
tome
> edn supports extensibility through a simple mechanism. # followed
> immediately by a symbol starting with an alphabetic character indicates that
> that symbol is a tag

OK, so it's trivial to add that as a constructor to the Haskell EDN type in
the post, and you can even support it in JSON with a dictionary like

    
    
        #myapp/Person {:first "Fred" :last "Mertz"}
    

is represented as

    
    
        {"#myapp/Person" : { "first" : "Fred", "last" : "Mertz" } }
    

What are the remaining objections?

~~~
dustingetz
EDN is much more like XML than it is JSON.

1) When I read #uri "[http://google.com"](http://google.com") my app code sees
(java.net.URI. "[http://google.com"](http://google.com")), not Tag "URI"
"[http://google.com"](http://google.com") or whatever. clojure.core/map does
not see tagged values, it does not know that the values were ever read from
edn.

2) Extension happens in userland library code, you don't need to go modify the
hardcoded pattern match in core. (Talking about reifying actual instances that
we can code to, not reader tags)

3) Data is just information. Information isn't coupled to code, it's abstract
values, totally separate from the concrete implementation. As RH says: "code
is data. But data is not code until you define a language around it."
Typeclasses are about code.

4) EDN values are cross platform and a platform's EDN reader can reify the
value into an idiomatic type for that platform. E.g. a haskell edn reader
could reify #error "foo" into Left String; a Java reader a Throwable to be re-
thrown later.

5) The whole prism diversion is sophomoric. Once you've read the EDN into
concrete values of whatever platform type, you can use whatever platform
abstractions you like to manipulate them. Clojure has lenses too:
[http://funcool.github.io/lentes/latest/#composition](http://funcool.github.io/lentes/latest/#composition)

You can watch the EDN talk or read the transcript if you'd like to learn more.
This topic is very deep but this thread is not doing it justice.

~~~
tome
I think it would be a great service if someone would write up a technical
introduction to this for non-Clojurists. You seem to be communicating a subtle
point that not many of us are getting ...

~~~
dustingetz
Just wrote this in r/clojure hth
[https://www.reddit.com/r/Clojure/comments/7a4qxp/dueling_rhe...](https://www.reddit.com/r/Clojure/comments/7a4qxp/dueling_rhetoric_of_clojure_and_haskell/dp8m4fk/?context=1)

RH already gave a great nontechnical introduction in the EDN talk in 2012, I
linked you a transcription of it upthread

------
enugu
Watching Hickey's talk, many of the complaints seemed to be valuable, _but
they didnt seem to be about static types_. Rather, it was about some problems
with existing data types locking one into a rigid data model when the domain
is constantly expanding.

This post by DeGoes makes the same point. [http://degoes.net/articles/kill-
data](http://degoes.net/articles/kill-data)

The default model of algebraic data types is too inflexible.

There are different extensions which work with this issue. Good record system,
Extensible cases, using free monads etc. We can have concise syntax for
automatically declaring an interface for a algebraic data type based on some
field values (ie, customizable deriving statements). Namespace qualified
keywords, so we have rdf like attribute based semantics.

The post doesnt respond to the issue but suggests that if you want to do the
same thing in clojure with error handling etc, you will need to think about
this stuff.

Also, Hickey's mention of Monads was again not about static types. Monad laws
are not typechecked. Their motivation is purity. The only slight inconvenience
in a dynamic context is that you dont have return type polymorphism, so you
have to type IOreturn instead of return.

~~~
tome
> [Monad's] motivation is purity.

This is not true and it's important to clear up this misconception lest anyone
thing "the only good reason to use monads in Haskell is because it's a pure
language".

* The use of monads in functional programming arose purely technically as an innovation in denotational semantics

* Then someone noticed you could use it to wrap up IO purely in Haskell

* Then it was noticed you could use it for _all sorts of other stuff_ besides dealing with IO in a pure language.

Monads are only a little bit related to purity.

~~~
enugu
Yes, sure, monads, applicatives etc. have plenty of applications beyond IO,
which is part of why keeping that abstraction separate from any particular use
is of value.

My point is that this doesnt have much to do with static typing vs dynamic
typing per se, as we dont check them statically. They are just important
examples of an interface with implementations for many data types, which can
be useful even in a dynamic language like Clojure. People who write a parser
in a dynamic language might benefit from learning about distinction between
applicatives and monads.

~~~
tome
Yes I agree. I just wanted to clear up a potential misconception.

------
christophilus
I wanted to like Haskell, but just never could get to the point where I
enjoyed using it. It always felt messy and complicated to me. I think the
language extensions were a contributor to these feelings. I also felt as if I
spent more time wrangling with the type system than actually solving my
business problems.

Yet I really do like Clojure, F#, and PureScript. There's an experimental C++
back-end to PureScript now [0]. I wonder if that will ever be a viable
production target?

Anyway, one of the things I like about PureScript is the row-types. Does
anyone know if there's a plan to get row-types into Haskell?

[0] [https://github.com/andyarvanitis/purescript-
native](https://github.com/andyarvanitis/purescript-native)

~~~
platz
I am unconvinced of the practical utility of what row types give you for the
added complexity.

The proposal to remove the Eff type (going back to IO) from purescript is
telling

~~~
wtetzner
Do row types add a lot of complexity?

~~~
platz
I think they add more complexity if you have to track and spell out each
effect that is being added or removed from the stack

------
aero142
I recently added Flow Type to a Javascript project and it changed my opinion
on this debate. I realized that I really don't care about static vs dynamic
typing. What I care about is a hierarchy of the ways that my code can be more
likely correct. Given the same level of verification, integration tests and
are worse than unit tests are worse than runtime checks are worse than compile
time checks. There may be cases where static typing makes code more
performant, but I usually care a lot more about development speed and
correctness. In this world, I just want a way of verifying my code as quickly
as possible. Gradual typing lets me specify some validations of my code that
will run at compile time. This is a huge win for me in both correctness and
development speed.

I don't know if we will ever invent the perfect static type system, but I do
know that having the ability to specify some types in a pretty good type
system, is better than not being able to specify any types.

I'm convinced that a language with a progressive type system is strictly
better than one without. Therefor, any debate that compares static vs dynamic,
instead of static vs progressive is not interesting to me.

------
emidln
Fwiw, you can map over Maps and Strings in clojure:

    
    
        (map identity "foo")  ;; a seq for String is its chars
        ;=> (\f \o \o)
        
        (map identity {:foo :bar}) ;; a seq of a Map is the
                                   ;; pairs of key/values
        ;=> ([:foo :bar])

~~~
brandonbloom
And get works on more types, like vectors. It also returns nil instead of
throwing:

    
    
        cljs.user=> (get [:x :y :z] 1)
        :y
        cljs.user=> (get 5 :x)
        nil

------
dustingetz
EDN ( _Extensible_ Data Notation) is extensible in userland, which is the
whole point of it. This is JSON plus some extra types.

.

.

.

r/clojure on JSON vs EDN:
[https://www.reddit.com/r/Clojure/comments/6gytlf/json_vs_edn...](https://www.reddit.com/r/Clojure/comments/6gytlf/json_vs_edn_what_does_rich_hickey_mean/)

Transcript of Rich Hickey EDN talk which OP obviously hasn't seen:
[https://github.com/matthiasn/talk-
transcripts/blob/master/Hi...](https://github.com/matthiasn/talk-
transcripts/blob/master/Hickey_Rich/LanguageSystem.md)

Transcript of Rich Hickey talk OP linked, C-f "edn":
[https://github.com/matthiasn/talk-
transcripts/blob/master/Hi...](https://github.com/matthiasn/talk-
transcripts/blob/master/Hickey_Rich/EffectivePrograms.md) Perhaps OP had his
fingers in his ears while he watched it. This blog post should be retracted
with an apology.

~~~
tome
Would you care to expand on that? It's not clear what you mean, neither from
your comment nor the linked Reddit post.

> Transcript of Rich Hickey talk OP linked, C-f "edn":

What do you mean? There are these three occurences of "edn", none of which is
enlightening.

* That's great, I'll start shipping some edn across a socket and we're done.

* How many people ever sent edn over wire? Yeah

* So the edn data model is not like a small part of Clojure, it's sort of the heart of Clojure, right? It's the answer to many of these problems. It's tangible, it works over wires

It sounds like he mostly cares about edn because of wires.

~~~
dustingetz
You have the primary source right in front of you!!!!!!!! What do you need me
to explain it worse for? Print out the damn paper, sit down with a highlighter
and read. FFS.

~~~
AnimalMuppet
He _looked_ at the primary source, and doesn't see it saying what you claim it
says. So he's asking you for where, from the primary source, you found the
source for making your claim. Given that he already looked at the source you
cited, that doesn't seem like an extraordinary request...

------
tome
What's _really_ sorely lacking from these discussions is concrete examples of
functionality that's easy to write in Clojure and hard in Haskell. I don't
mean functions like `assoc-in`. I mean real functional parts of programs.

~~~
dustingetz
It's about the way you think, not about what you can and can't do. Haskell
lets you safely think the really complicated types needed to do programming
with zero effects; you couldn't think those thoughts without Haskell because
the types we use today are too complex. Clojure encourages you to think in
terms of data, to push as much logic as possible out of the code and into the
data, and then write simple programs to transform that data.

[http://hyperfiddle.net/](http://hyperfiddle.net/) (my startup) is an example
of a data driven system. Hyperfiddle itself is implemented as a large amount
of data + 3000 loc to interpret it. If the system is only 3000 loc, you're
really not at the complexity scale where all that category theory gymnastics
really pays off.

~~~
tome
That's not especially convincing to me. Haskell also encourages me to "think
in terms of data, to push as much logic as possible out of the code and into
the data, and then write simple programs to transform that data.".

~~~
dustingetz
c-f "degoes" in this thread

~~~
tome
You know John De Goes is a massive Haskell proponent, right?

------
grandalf
This is great. Why have I never heard anyone make this argument over beers
when the debating starts?

Seems like critiques of a programming language or paradigm are usually made by
someone imagining a very bad codebase from their past.

------
wz1000
A dynamically typed language is a statically typed language with precisely one
type.

It is extremely easy to use haskell in "dynamic mode". Just use
`ByteString`(or Data.Dynamic for safety/convenience) for all your data. Types
just present a way to encode some statically known guarantees about the
structure of your data/code. You are free to not encode any properties if you
want to.

But it is very rare that the data you are working with requires the full
generality of `ByteString`. You usually have _some_ sort of structure rather
than just working with strings of zeros and ones.

~~~
brandonbloom
> A dynamically typed language is a statically typed language with precisely
> one type.

While technically true, saying this is about as useful as saying "You can do
anything in any Turing-complete programming language."

~~~
kod
No, it's really not equivalent to cracks about Turing completeness.

Doing "dynamic" typing in a static language requires me to add all of 5
characters, e.g. ": Any"

Doing static typing in a dynamic language requires me to write a type checker.

These are nowhere near the same.

~~~
bad_user
Being able to work with "Any" implies either working with Strings (since you
can encode anything in strings) or it implies a memory unsafe language (e.g.
working with void* in C) or it implies subtyping and hence an OOP language.

But OOP subtyping is already about solving polymorphic call sites at runtime.
And because you carry a vtable around for every instance, thus objects being
tagged with their classes, you can always do upcastings and downcastings. So
OOP languages are already very dynamic on that scale and fairly unsafe.

No, you cannot do " _: Any_ " in Haskell.

~~~
tome
> No, you cannot do ": Any" in Haskell.

[https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-...](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-
Dynamic.html)

~~~
tome
On second thoughts I think kod used Any to mean something like Dynamic and
bad_user assumed Any meant a top type, something like I believe Scala has.

------
weavejester
_" If EDN is an improvement over JSON, then it is marginal at best."_

Why is it only a marginal improvement? It adds considerably more semantic
information.

 _" Utilizing EDN also promotes a lot of invisible coupling. Some may tell you
that dynamic types don’t couple, but that is incorrect and shows a lack of
understanding of coupling itself. Many functions over Map exhibit external and
stamp coupling."_

Coupling implies a bidirectional connection. Functions rely on data types, but
not vice versa.

------
brandonbloom
EDIT: deleting this post because it was needlessly counter-inflamatory.

~~~
platz
No need for typeclasses/existentials. That is trying to approximate some
typed/untyped middle ground. You would just use 'dynamic' if you want true
dynamic behavior

~~~
brandonbloom
The Edn type given here is closed. That's a correct definition of Edn, which
is a closed sum. Edn accomplishes extensibility via the Tag type. However, not
all Clojure data is Edn. In order to implement the clmap and clget functions
with their full generality, they need to support an open set of types. For
example, both `#inst "..."` and `(eval '(Date. ...))` are separate types:
TaggedLiteral and java.util.Date respectively.

You need either Dynamic or existentials because Clojure enables you to pass
data structures between two functions expecting collection elements of
differing capabilities without either A) whole program / inter-module analysis
or B) an O(N) type translation.

~~~
platz
Right.

