
Thinking in Types - Croaky
http://robots.thoughtbot.com/thinking-in-types
======
tel
Heterogenous lists are always coming up when people try to translate OO ideas
into Haskell. It's great that this example used the barrier of heterogeneity
as a reason to think harder about their design instead of barreling forward.

In particular, heterogeneity causes a form of information loss via type
erasure (existential typing). The problem is that this is pretty heavy
machinery and is not always well-suited to such a simple problem as a
heterogenous list. Additionally, once you've produced a type like

    
    
        [forall a . Renderable a => a] -- not a valid Hs type, but close
    

frequently you've reduced your options for what to do down to really just a
single choice. In this case, `render`. This is what's sometimes called the
Existential Antipattern—if you have an existential type where only one way
forward remains... you may as well just take that way forward. This is
especially easy in a lazy language like Haskell.

    
    
        renderEm :: Ball -> Player -> Player -> [IO ()] -- homogenous!
        renderEm b p1 p2 = [render b, render p1, render p2]
    

Note that this is functionally very similar to the implementation of `render`
for Game as given in the article. In particular, the only difference is that
the article's `render` method destroys the barriers between these `IO` actions
by sequencing:

    
    
        instance Render Game where
          render (Game b p1 p2) = sequence (renderEm b p1 p2)
    

where `sequence` sequences a list of monadic actions

    
    
        -- try thinking about this as "distributing" a monad over a list
        -- much like you distribute multiplication over addition
        --
        -- x (a + b) --> x a + x b
        --
        -- and then go look up Data.Traversable
     
        sequence :: Monad m => [m a] -> m [a]
        sequence [] = return []
        sequence (ma:mas) = ma >> sequence mas
    

This tension between a "reified list of actions" and a composite action is
pretty much the heart of the expression problem—the reified list is an
_initial_ encoding and the final action the final encoding. This kind of thing
shows up all the time when dealing with heterogenous lists. The reason being
that OO basically favors final encodings to the extinction of initial ones...
but initial encodings are what generate type information.

~~~
dllthomas
_" [I]f you have an existential type where only one way forward remains... you
may as well just take that way forward. This is especially easy in a lazy
language like Haskell."_

This doesn't seem especially _easy_ in Haskell (in that it doesn't seem harder
elsewhere), but especially _the case_ a lazy language like Haskell. In an
eager language, (forall a . Renderable a => a) is isomorphic to (() -> IO ())
and subtly _distinct_ from (IO ()).

~~~
tel
I think on second thought, the easy comes more from purity than laziness.

~~~
dllthomas
I think I agree. Especially where safe implies easy.

------
jarrett
On a related note: The article (quite reasonably) avoids discussion of the
graphics library, but I want to know more about that side of things. I wish
graphics got more attention in the Haskell ecosystem in general.

The options right now are pretty dismal. There is quite literally not a single
Haskell graphics or GUI package that I've been able to install on OS X. I'd
love to use Haskell to build games or desktop GUI apps, but without a library
for OpenGL, windowing, etc., it's not practical.

This isn't just whining, though. I bring this up to ask: What can I, as a
relatively green Haskell developer, do to improve the situation? Is there a
realistic path for a new Haskell user to get involved in the package
ecosystem?

I'd be willing to bet that a great many developers like me wanted to try
Haskell, but gave up when they found out how many Cabal packages don't
compile. I think fixing that would do a lot for Haskell's mainstream
acceptance.

~~~
sgerrand
Have you looked at the Haskell QML bindings? They generally work for cross
platform requirements.

[http://hackage.haskell.org/package/hsqml-0.1.1/docs/Graphics...](http://hackage.haskell.org/package/hsqml-0.1.1/docs/Graphics-
QML.html)

~~~
jarrett
Yes. It does not compile on my Mavericks machine. Same with every graphics
package I've tried.

~~~
dllthomas
Get in touch with the developers and work with them to get it building. Often
times they don't have access to your platform, so just being that helps.
Anything you can do on top of that is gravy.

~~~
VMG
While what you suggest is reasonable and the right thing to do, it just
highlights the original point. The state of UI bindings is pretty bad.

~~~
dllthomas
I wasn't making any point. Fwiw, I have been able to meet my own (quite
limited) gui needs in Haskell with gtk...

Edit: On reflection, I find your assertion a little strange here. Anecdotal
failure to build on one particular setup doesn't seem like a particular
highlighting of bad bindings.

~~~
mokus
I can add another anecdote. In almost 10 years now of using Haskell on Mac,
Linux and Windows, I've never once had any success building any GUI binding
for Mac OS, despite trying every one I could find (gtkhs included) at least
once a year on many different OS/hardware combinations.

(unless you count HOC, but I never actually got it working as-was - I brought
it back from bit-rot on a couple occasions, adding ObjC 2 support and
rewriting a fair chunk of the low-level stuff, but never did find the time to
update the fragile header-parsing stuff for the actual Cocoa-binding
generator)

~~~
dllthomas
Huh. It _is_ sounding like there's an issue with Mac GUIs, then. Maybe the
Mac/Haskell overlap is just too small? Both are small-to-niche... It would
probably make sense to get a group oriented around getting that fixed up. For
myself, I'm meeting my needs - and both 1) more GUI apps and 2) more Mac
support are almost entirely orthogonal to them, so I'm not likely to
participate.

~~~
jarrett
> Maybe the Mac/Haskell overlap is just too small?

Probably so. Though I would suggest that the neither one is a small niche in
itself. Mac especially--it's the favored platform for every developer I know
except one. Yes, it's less popular outside tech circles (probably due in part
to the price).

Imagine this scenario: There are tons of Mac users who want to learn Haskell.
They try it, but can't install libraries. The Haskell community never hears
from them; one could say the system failed silently. Meanwhile, Linux works
fine, and the Haskell-Linux community keeps growing.

So perhaps there's a self-reinforcing Linux-centric bias in the developer
population.

~~~
dllthomas
I wouldn't at all say either is "a small niche." As niches go, they're both
pretty large... I actually have no idea how the prevalence of Mac differs
inside and outside tech circles. It's a decided minority in every case, with
Windows still dominant and likely Linux still dominated (though I'm far less
confident about that in dev circles than I used to be). For what it's worth,
virtually every developer I know well enough to know what they prefer uses
either Linux or Windows, with the exception of my mother who decided some few
years back that Mac is "Unix enough" now. I expect that there's a lot of
clustering, though, and neither of our experience represents a uniform
sampling.

Your general point - that it's likely self-reinforcing - is certainly strong.
I'd even expect it to be exacerbated a bit in this case by it being GUI things
in particular showing issues, where (at the risk of stereotyping) there is
probably a correlation between those who prefer a Mac and those who prefer a
GUI.

~~~
jarrett
> virtually every developer I know well enough to know what they prefer uses
> either Linux or Windows

Interesting. It must be clustered, as you say.

I use a Mac largely because there are a handful of professional apps that
don't run on Linux. (Otherwise, I'd probably go with Mac hardware and a Linux
OS.) Which implies that the demands of my industry is what pushed me (and
perhaps the people I know) onto Macs.

> there is probably a correlation between those who prefer a Mac and those who
> prefer a GUI.

It's not so much that I personally prefer a GUI. (I don't, in general.) It's
that I make a lot of software for other people, so GUIs aren't a matter of
preference but of professional obligation. Also, there are certain
applications I'd like to do where a GUI is pretty much the only sensible
option: Design tools, certain types of games, etc.

------
AnimalMuppet
I'm not sure that this works in more general cases, though.

If I have a more general game with more objects of more types in it, adding
each type to the render function is going to get old. In object oriented
programming, I'd just call render() on each entry in the list of game objects.
But this approach is going to lead me to:

\- render each entry in the list of Foo objects

\- render each entry in the list of Bar objects

\- render each entry in the list of Baz objects

\- ...

which doesn't seem to me to work out very well as the game grows more
complicated.

(Sorry about all the line breaks - I can't seem to figure out how to get HN to
display it right without them.)

~~~
andolanra
My preferred way to approach this, in Haskell specifically, is to use records
as a naïve encoding of objects or interfaces. For example, expanding on the
functionality a little bit:

    
    
        data GameEntity = GameEntity
            { render      :: IO ()
            , getPosition :: Point
            , setPosition :: Point -> GameEntity
            }
    
        makeBall :: Point -> GameEntity
        makeBall pos = GameEntity { render      = myRender
                                  , getPosition = pos
                                  , setPosition = mySetPos
                                  }
            where myRender = {- draw the ball somehow -}
                  mySetPos newPos = makeBall newPos
        
        {- and similar for makePlayer -}
    

That means that the rendering code will look like

    
    
        renderAll :: [GameEntity] -> IO ()
        renderAll allEntities = mapM_ render allEntities
    
        main = do
          pl = makePlayer 1 (0, 5.0)
          p2 = makePlayer 2 (10.0, 5.0)
          b  = makeBall (5.0, 5.0)
          {- ... -}
          renderAll [p1, p2, b] -- type-checks correctly now
    

What I've done is used a record type to encode the interface that it's
supposed to expose, while hiding exactly what the particular implementations
of the interface are. It's also nicely extensible; if I wanted to add in some
other kind of entity—say, a turtle—all I'd need to do is add a function like

    
    
        makeTurtle :: Point -> ShellColor -> TurtleDisposition -> GameEntity
    

and nothing else need be changed.

~~~
zvrba
> What I've done is used a record type to encode the interface that it's
> supposed to expose,

I love how Haskellers (in general, not you in particular) bash OO and then
come up with the exact same technique of simulating OO that is used in C (a
struct of function pointers). Something which OO languages provide out of the
box (interfaces, virtual methods, etc).

Now, let's take this a step further: how would you simulate double-dispatch,
or multiple dispatch in Haskell? This is sorely missing from mainstream OO
languages.

~~~
tel
I don't think Haskellers bash OO that much. They bash some of the trappings of
it. GoF is usually a typesafe library in Haskell because it has better
abstraction capabilities. Functions form better fundamental units than objects
do (but are a form of "object" themselves). Composition is better than
inheritance. Classes are not generally so useful.

Really, I think Haskell has a super great object system. It's just (a) not the
central conceit and (b) so naturally embedded in the language that you can
program for a long time without even noticing it's there.

Codata typically indicates you're looking at an object. In Haskell, due to it
being non-terminating as a language, codata and data are unified so most
"objects" look identical to their non-object form. You can also notice them by
definitions based on eliminators. Automata are a good example

    
    
        newtype Auto f i o = Auto { run :: i -> f (Auto f i o, o) }
    

You can also look for objects as ADTs. When you do this you can get a natural
embedding of a prototype-based language.
([http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf](http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf))

    
    
        data ADT f = forall st 
                   . ADT { unfold :: st -> f st
                         , state  :: st
                         }
    

Here, the internal state type `st` is closed over as an existential value—when
you create a new ADT you can pick what st is but the type system then ensures
that _nobody_ ever can access that type again. Instead, you have to use the
`unfold` elimination form which projects the state into a "class" `f`
(represented as a Functor, but that's immaterial) which defines a signature of
methods over the abstract state. Auto, from before, is definable this way.

    
    
        data AutoClass st o = 
          AutoClass { next :: st
                    , out  :: o
                    }
    
         type Auto = ADT AutoClass
    

Subtyping occurs naturally with quantified types like the existentially-typed
`st` variable or the universally quantified types you see all the time in type
signatures. It's a more natural form of subtyping since it's all defined by
increasing typeclass bounds. Something like

    
    
            {C} : set of constraints    Ci : constraint
        ------------------------------------------------------
        forall a . {C} a => a :> forall a . ({C} a, Ci a) => a
    

This guarantees that these these two types are good "subtypes on eliminators"
which satisfies the Liskov Substitution Principle if not some of the more
strict definitions of subtype "niceness".

Double/Multiple dispatch is trivially handled in Haskell since polymorphism is
solved by an entire _Prolog_ embedded in the type system.

The thing is that Haskell also has initial data, things like finite lists are
better thought of as a big tree of constructors

    
    
        1:(2:(3:(4:(5:[]))))
    

and then pattern matched upon (defining catamorphisms)

    
    
        sum :: [Int] -> Int
        sum (a:bs) = a + sum bs
        sum []     = 0
    

and this pattern is perhaps a little bit emphasized in mainstream Haskell
because it sides on the "functional" side of the expression problem. It
doesn't mean, though, that Haskell doesn't like the "object oriented" side,
but instead that Haskell favors both.

------
woah
Seeing so much stuff about Haskell lately, but there seems to be a curious
dearth of actual software written in it, if it's so great. How is it that
janky hacked together languages like JS and PHP have huge numbers of projects
built with them, while a supposedly superior language like Haskell is mostly
academic? If it really makes you that much faster, where are the apps?

~~~
badman_ting
Honestly, it seems to me like if you don't understand category theory and type
theory well, using Haskell will be either hard or impossible. That's what
people who are into Haskell are into, and they seem to be a relatively rare
breed. (I have a lot of trouble understanding these subjects, though I
continue to try. I still don't know what the hell a monad really is.)

~~~
dllthomas
Do you want to know "what a monad is" in math or Haskell?

In programming, a monad is a particular design pattern, which is exposed in a
particular interface (appropriately called Monad) in Haskell. The design
pattern supports a certain way of combining things. The fact that so many
disparate things support this interface (State, IO, Software Transactional
Memory, Readers, Writers, Continuations...) means that all of the code we
write that generically talks to that interface can talk about any of those
things, and that's pretty powerful. The fact that one of those things is, in a
certain sense, voodoo (IO) shouldn't lead you to think they all are - Monad is
just an interface for combining things according to certain patterns.

------
laureny
I'm getting tired of reading:

> This is why we hear that Haskell reprise if it compiles, it works.

If this were true then functions would not need bodies, you would just define
their signatures and move on with life.

The truth is that even with its superb type system, Haskell still needs to run
your code. Your code might be statically correct but its runtime is up to you.

I would prefer it if people rephrased this claim like "If it compiles in
Haskell, it's more likely to run than if it compiles in Java".

More honest.

~~~
tel
Well, as you go to the next steps you can use proof search techniques to do
exactly that: write your types and your programs write themselves as the "only
possible implementation".

This is already possible sometimes in Haskell so long as we restrict ourselves
from pathological values like exceptions and non-termination. In fact, the
first place this phrase shows up is Russel O'Connor talking about highly
polymorphic lens code.

[http://r6.ca/blog/20120708T122219Z.html](http://r6.ca/blog/20120708T122219Z.html)

This kind of type limitation of possible implementations is called
_parametricity_ and is difficult to encounter even in most typed languages as
it requires purity.

~~~
laureny
> write your types and your programs write themselves as the "only possible
> implementation".

But there are only very few type signatures for which this is true, even if
you stick to totality.

~~~
tel
In practice types often winnow the possible implementations to be a relatively
small set. This effect is improved if you also include notions of law-abiding
implementations as Haskellers often do. At the end of the day, it's true that
implementations (don't yet) write themselves, but, more realistically, that
the constraints of type and theory drive you naturally to the correct
solutions even if you never once figure out the "operational" aspect.

 _That_ occurs quite often.

~~~
ademarre
> _law-abiding implementations_

Are you referring to category laws?

~~~
dllthomas
Lots of typeclasses have associated "laws" that well-behaved instances are
expected to abide by.

~~~
laureny
But these are not enforceable by the type system (at least in Haskell), kind
of supporting my point that types alone are rarely sufficient :)

~~~
dllthomas
I don't think anyone believes that types are sufficient outside, at least
outside of a dependently typed language (at which point you'll have more
diversity of opinion).

------
badman_ting
_" A type class defines a set of functions which must be implemented for a
type to be considered in that type class. Other functions can then be written
which operate not on one specific type, but on any type which is in its given
class constraint."_

Call me crazy but this just sounds like a Java interface to me.

Edit: On further thought, I guess the difference is that in Java, the
interface itself is a type. So all instances of classes which implement
_IShowable_ can be said to be of type IShowable. Whereas it seems that in
Haskell a typeclass is not, itself, a type.

~~~
tel
There are a few other differences as well. I'm not completely familiar with
the ins and outs of Java interfaces, but:

    
    
        * You can instantiate types to classes at any point in
          time (type definition, class definition, or even orphan 
          instances, though that last category is frowned upon)
    
        * Typeclasses indicate typing bounds but do not destroy 
          type information. This means that we can define 
          things like
    
            showableId :: Show a => a -> a
            showableId x = x
    
          which allow only showable types to pass but does not 
          destroy type information
    
            > showableId (3 :: Int)
            3 :: Int
            > showableId (id :: Int -> Int)
            !! Type error
    
        * Typeclasses can dispatch on *any* type in the signature.
          This includes the famous "return type polymorphism" but
          generally means that typeclass resolution involves 
          solving a terminating form of Prolog during 
          typechecking. This means that type information flows
          forward and backward over judgements and allows for
          greater inference.
    
        * Typeclasses can abstract over higher kinded types. So we
          can write something like
    
            count :: Traversable t => t a -> Int
            count = getSum 
                  . getConst 
                  . traverse (const $ Const (Sum 1))
    
          which generically counts the elements in any container
          instantiating the "interface" Traversable.
    

There's also some even funkier techniques you can use when you start involving
MultiParamTypeClasses, FunctionalDependencies, or TypeFamilies.

------
platz
Frequently there will be many different ways to solve or architect a problem
in Haskell, for better or worse depending on your viewpoint. Personally I
think it it is better.

But anyways, another option would be to make a type for your list which wraps
each possible element, then you can just pattern match on the ADT.

Also slightly concerned that this has language targeting beginners with almost
0 Haskell knowledge: I am not sure quick anecdotes like this will do more to
help than confuse complete beginners.

I still think those interested should start with LYAH which does a good job
giving enough context with the flurry of new things to learn.

I suppose it's inevitable if haskell gets more popular there will be more
posts like this (which i like) but I'm not sure if its the right way to
onboard newcomers.

~~~
zmoazeni
I'm all for more posts like this and I hope we see more. I think it helps tear
down the misconception that Haskell is only for people who do a ton of
research beforehand.

~~~
platz
Fair enough, I'm for whatever works!

------
sparkie
> If we’re careful in our module exports, a change like this can be done in a
> backward-compatible way. Such an approach is outlined here
> ([http://www.yesodweb.com/blog/2011/10/settings-
> types](http://www.yesodweb.com/blog/2011/10/settings-types)).

The problem with this import solution is that _almost nobody actually does
it!_ Nearly every module you will ever import exposes most of the constructors
of the ADTs they define - because Haskell encourages it - it's much simpler
and cleaner to pattern match over constructors than the functions you use in
place of them in order to encapsulate the constructors - which you need to use
guards to match over instead.

~~~
andolanra
You could also pattern-match over them with View Patterns[^1], i.e. export an
alternate ADT that is the 'acceptable' view on the data and a function that
takes the encapsulated implementation and turns it to the alternate
representation—but I have literally never seen this done, save in the
documents describing the motivations for View Patterns.

[^1]:
[https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns](https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns)

------
zwieback
I think a better example is needed. The example's solution to the Haskell
heterogeneous list "problem" could be implemented in other statically typed
languages (C++, Java) although it wouldn't be necessary to do so.

I don't see how the Haskell type system is safer in this example but I feel
there's something interesting there which I don't understand. Can someone
explain the advantage of type classes over what could be done with interfaces
and templates in C++?

~~~
mcguire
" _...although it wouldn 't be necessary to do so._"

That's actually the point. (Have you read the "wearing the hair shirt" paper?)

One of the hardest problems in moving from other languages to Haskell is that
many of the solutions that you would immediately default to rely on run-time
information or behavior, like the heterogeneous list. Those don't translate
well, if at all. Instead, you need to step back and change the problem, by
relying more on compile-time, type-level information.

------
dllthomas
_" It’s impossible to run a Haskell program with a type error"_

Unless you ask for it!

~~~
DanWaterworth
Yes, see:
[http://www.haskell.org/ghc/docs/7.8.1/html/users_guide/defer...](http://www.haskell.org/ghc/docs/7.8.1/html/users_guide/defer-
type-errors.html)

