
Clojure's core.typed vs Haskell - llambda
http://adambard.com/blog/core-typed-vs-haskell/
======
freyrs3
A simple program is an interesting comparison, but it's kind odd to consider
this to compare the whole typesystem. It's hard to understate that GHC has a
/big/ typesystem and has had a significant amount of research done in it over
the past 10 years and a a result there are a lot of very powerful non-trivial
typing constructs. I think the real testament to the power of Haskell's
typesystem is how well inference still works even in the presence of most of
these non-trivial extensions.

~~~
adambard
Really, the post is about Clojure; these toy problems represent the breadth of
my experience with Haskell, so I'm not really qualified to go into more depth.
Certainly Haskell is said to have the most powerful type system going, and I
don't see any (mainstream-ish) language displacing it any time soon.

Still, I think core.typed does pretty damn well for being a standalone
library.

~~~
acomar
> Certainly Haskell is said to have the most powerful type system going,

Nah, Coq, Agda, Idris and any dependently typed language have Haskell beat --
their type systems are designed to be just as expressive as their value
language. Haskell is certainly moving in this direction, especially with
modern extensions. The issue is of course type inference breaks down in the
face of these extensions, and Haskell is trying to maintain its powerful type
inference as best it can. Certain combinations of extensions already force
type annotations.

> Still, I think core.typed does pretty damn well for being a standalone
> library.

This is true, and I think the comparison to Haskell actually hurts the greater
point you're trying to make. Haskell has full static types at its disposal,
and comparing them to the optional typing of Clojure will always leave
something wanting -- why draw that comparison when competing with Haskell is
not the intention of the library?

~~~
chongli
_Nah, Coq, Agda, Idris and any dependently typed language have Haskell beat --
their type systems are designed to be just as expressive as their value
language._

That may be true, but it comes at a big cost: inference and decidability.
Haskell's type system is designed to very carefully butt up against the limits
of these features. Once you go full-spectrum dependent types, you lose all
that.

~~~
acomar
Agda and Coq retain inference and decidability by sacrificing Turing
completeness. Terminating programs must terminate provably, and non-
terminating programs must make concrete progress on every iteration. I'm not
familiar enough with Idris to say how it tackles this issue - I do know that
it _is_ Turing complete.

So with that said, you're absolutely right.

~~~
polymatter
Another language with a Turing complete type system is Shen [1] (previous life
as Qi). Just wanted to put that out there as its a Lisp dialect that really
pushes Lisp out there.

[1] [http://shenlanguage.org/](http://shenlanguage.org/)

~~~
autodidakto
When it comes to awesomeness to fame ratio, I can't think of a language with a
higher one than Shen. It's odd how little people talk about it. I,
unfortunately, suspect it has to do with the people leading it.

~~~
tel
I've looked at Shen a few times and, honestly, I always get turned off by the
syntax. There's not enough out there explaining why I should continue past
that concern, and it seems to throw out what's nice about Lisp syntax in order
to get halfway to Haskell's.

I'm sure I'll take a look at it again sometime, but I'd really love some kind
of intro that helped me to understand why it was worth the time investment to
get to know it.

------
tikhonj
If you really want it, you can get _safe_ dynamic typing in Haskell using the
Data.Dynamic module. However, since you _don 't_ really want it, it's a
library that isn't introduced in basic tutorials.

This gives you a pragmatic equivalent to no-check, but is almost never used in
practice because it turns out to be unnecessary.

~~~
dmytrish
Also, GHC from version 7.6.1 supports a flag to defer type errors until run
time[1]: -fdefer-type-errors. This is not support for dynamic types, but still
gives even more flexibility in type-error handling.

[1]
[http://ghc.haskell.org/trac/ghc/wiki/DeferErrorsToRuntime](http://ghc.haskell.org/trac/ghc/wiki/DeferErrorsToRuntime)

------
zeckalpha
I'd be curious to see how Typed Racket compared, too.

~~~
ambrosebs
There would be annotations in the same places as Typed Clojure, except :no-
check would be replaced by type soundness-preserving runtime assertions. There
would be no need for :no-check anyway; Typed Racket's base annotations are
complete AFAIK.

~~~
axle_512
It is a little clearer to me how this works with things like primitives, but
how do you annotate a function that takes a map as input and returns a list?
(defn my-keys [m] (keys m))

Edit: Just found Seqable. Looking into it now. This link helped me a lot.
[https://github.com/clojure/core.typed/wiki/Types](https://github.com/clojure/core.typed/wiki/Types)

~~~
ambrosebs
(ns my-ns (:require [clojure.core.typed :as t :refer [ann]]))

(ann my-keys (All [a] [(t/Map a Any) -> (t/Coll a)])

~~~
axle_512
nice, thanks!

------
masklinn
> As seen at the top, it was necessary to annotate mod. It has the no-check
> flag on it, which is basically how you tell core.typed to just take your
> word on this one. That's something you can't do in Haskell, but I'm not sure
> whether or not that's a good thing.

Isn't the reason why you need to do it because you're importing a non-typed
symbol? Wouldn't the only situation where you'd need to do that in Haskell be
at FFI?

(and a nitpick: `putStrLn $ show` can be replaced with a simple `print` call)

~~~
rmcclellan
Of course you can do this in Haskell:

[http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base/Un...](http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base/Unsafe-
Coerce.html#v:unsafeCoerce)

~~~
brandonbloom
I'll save the value judgement for another time, but I'd like to point out an
important difference:

As the word "unsafe" implies, these Haskell primitives forego type _safety_ in
addition to type correctness. That means you can get segfaults and other
undefined behavior at runtime. Such a type error on the JVM will simply
produce an exception at runtime.

~~~
tikhonj
This is why we have Data.Dynamic which does _safe_ dynamic types.

It almost never comes up because it turns out to be virtually useless, but
that's a story for another time.

~~~
brandonbloom
> it turns out to be virtually useless

That's debatable, however Data.Dynamic is built on top of Data.Typeable, which
provides a lower level runtime type safety facility. I think we can both agree
Typable has lots of interesting uses.

~~~
tel
Typeable is interesting in theory and generic traversals are a godsend, but
usually I find that when I'm reaching for that particular hammer I should
check twice.

~~~
brandonbloom
I'll agree it's best to avoid fully open unions when you can, but some (super
useful) things truly don't work that way. Check out
[http://okmij.org/ftp/Computation/monads.html#ExtensibleDS](http://okmij.org/ftp/Computation/monads.html#ExtensibleDS)
for a cool example.

~~~
tel
I always think of Control.Exception as the poster child for open unions.

~~~
brandonbloom
Perfect, since exceptions are a subset of effects! Check out
[http://math.andrej.com/eff/](http://math.andrej.com/eff/) and its literature.

------
adestefan
> Integer is less useful as a type than hoped. AnyInteger seems to be more
> permissive, and works more often. I'm sure there is a good reason for this.

This is with about 15 minutes of looking at core.typed. Isn't this because the
type Interger is actually java.lang.Integer, but (type 1000) is
java.lang.Long?

Also, AnyInteger isn't that permissive. It's defined as (U Integer Long
clojure.lang.BigInt BigInteger Short Byte). The big difference would be that
Haskell's Int is bounded so AnyInteger is more like Haskell's Integer.

~~~
adambard
That would be the reason, yeah. I had forgotten that Clojure tends to use
Longs for everything.

------
jahewson
The Haskell could use a list comprehension...

    
    
        divBy x y = y `mod` x /= 0
        divBy3or5 x = divBy 3 x || divBy 5 x
        euler1 n = sum [x | x <- [0..n], divBy3or5 x]
        
        main = print $ euler1 1000
    

Personally, I'd switch out lines 2 and 3 to:

    
    
        euler1 n = sum [x | x <- [0..n], divBy 3 x, divBy 5 x]
    

Compared with the Clojure (correct me if I'm wrong):

    
    
        (defn euler1 [n] (reduce + (filter (fn [x] (or (div-by 3 x) (div-by 5 x)))) (range n)))
    

Edit: Note - Clojure code doesn't fit into a HN one-liner.

~~~
adambard
You could shave off a precious few characters using the function reader
syntax:

    
    
        (defn euler1 [n] (reduce + (filter #(or (div-by 3 %) (div-by 5 %)))) (range n)))
    

Or if you're into comprehensions:

    
    
        (defn euler1 [n] (reduce + (for [x (range n) :when (or (div-by 3 x) (div-by 5 x))] x)))

------
lelf
Apples vs vacuum cleaners

------
lysium
Is this (at the end of problem 1)

    
    
        (defn div-by [x y] (== 0 ))(mod y x)
    

a typo or a Clojure feature?

------
IsTom
Not being familiar with core.typed: does it support parametric polymorphism?
Can you dispatch based on types (e.g. typeclasses)?

~~~
munificent
Yes, Clojure has "protocols", which allow multiple dynamic dispatch on generic
functions.

[http://clojure.org/protocols](http://clojure.org/protocols)

~~~
chongli
Right, but protocols are not based on core.typed's annotations. There is no
way to dispatch on the return type of a function, for example.

~~~
brandonbloom
> There is no way to dispatch on the return type of a function, for example.

A feature, in my opinion.

See
[https://news.ycombinator.com/item?id=6459968](https://news.ycombinator.com/item?id=6459968)

~~~
chongli
_A feature, in my opinion._

Well, Clojure's a different language so it doesn't matter as much. If Clojure
had enforced purity and pervasive laziness (a la Haskell) it'd be a real pain
to use monads without polymorphic operators for them.

~~~
brandonbloom
You're assuming that Haskell's monadic approach is the ultimate approach to
managing effects (which include delayed evaluation). Personally, I prefer the
model in Eff [1] and proven as an alternative to monad transformers (although,
still a single-layer monad in Haskell) [2]. Eff's approach utilizes
capabilities [3], called "effect instances", to restrict access to effects.
Much nicer, in my opinion. Hence, I've been experimenting with the ideas in
Clojure [4].

[1]: [http://math.andrej.com/eff/](http://math.andrej.com/eff/)

[2]: [http://lambda-the-ultimate.org/node/4786](http://lambda-the-
ultimate.org/node/4786)

[3]: [http://erights.org/elib/capability/ode/ode-
capabilities.html](http://erights.org/elib/capability/ode/ode-
capabilities.html)

[4]:
[https://github.com/brandonbloom/cleff](https://github.com/brandonbloom/cleff)

~~~
chongli
I don't know enough about the theory to argue either way. I just really like
Haskell's monad libraries (as well as applicative functors and lenses). If
you're able to build a library that's as general, flexible and powerful in
Clojure I will be really excited!

------
segmondy
What a waste of an article. 8 lines of code for haskell, 19 lines for clojure.
The problem is easily solved in one line. I seriously don't care that's he
comparing core.type, when you start trying to write langauges the same way,
you lose the advantage that they provide.

1 line of python.

print sum(filter(lambda x: (x % 3 == 0) or (x % 5 == 0), range(1, 1000)))

clojure

(apply + (filter #(or (= 0(mod % 3)) (= 0(mod % 5))) (range 1 1000)))

~~~
Fishkins
In Haskell it could be sum $ filter (\x -> mod x 3 == 0 || mod x 5 == 0)
[1..1000] but I don't think that detracts from the article. He said up front
his solution was over-engineered. That's nice in this case, because it lets us
see how the type annotations look for very small, simple functions so we can
get an idea what they looked like. I know some Haskell and Clojure, but I'd
never actually looked at core.typed. This was a nice, basic intro to it.

~~~
jahewson
Even better, you can save 7 characters with:

    
    
        sum [x | x <- [1..1000], mod x 3 == 0, mod x 5 == 0]
    

But as you say, that wasn't the point of the article.

~~~
tel
This version is easily the clearest yet.

