begriffs

Tricking Haskell into being dynamic

August 16, 2013

Day 1. What do outsiders think of this language? Speaking for myself, I’ve always heard it has a strict type system that people learn to love. Today I want to use a simple example to bend the rules and get to the bottom of it.

I began Learn You a Haskell, and started typing in mundane stuff.

ghci> -- yawn
ghci> 5 == 5
true
ghci> 5 == "a string"
 
<interactive>:3:1:
    No instance for (Num [Char]) arising from the literal `5'
    Possible fix: add an instance declaration for (Num [Char])
    In the first argument of `(==)', namely `5'
    In the expression: 5 == "a string"
    In an equation for `it': it = 5 == "a string"

The language of course gently steps in and protects me. Comparing Nums and a list of Chars is nonsense. Yet…what if we wanted Haskell to evaluate this comparison and return false? I’m fully aware this is a bad idea, but it might teach us about how Haskell’s type system really works.

So let’s unwind how the type error above originates. What does the operator == expect?

ghci> :t (==)
(==) :: Eq a => a -> a -> Bool

This is really the crux of the problem, it grabs onto a type (which incidentally must be an instance of the Eq type class) and expects it for both arguments. I guess we have to give up, because a number and a string simply cannot be simultaneously substituted for a in the type signature above unless we can somehow make a new type which tricks Haskell. That would be magical, wouldn’t it? Something like

ghci> -- our goal
ghci> Magic 5 == Magic "a string"
false

Luckily most types are instances of Show, and provide a show function to represent themselves as strings. Turns out this is what the repl does when it prints values back. So we could compare Show instances by comparing their string representation.

ghci> show 5 == show "a string"
false

Maybe this is good enough. But maybe we can put it inside a magical type and partially obscure the implementation. We could make different data constructors and write all kinds of messy combinations for implementing equality tests.

data Magic = MagicString String | MagicInt Int

instance Eq Magic where
    (MagicString x) == (MagicString y) = x == y
    (MagicInt x) == (MagicInt y) = x == y
    (MagicInt x) == (MagicString y) = (show x) == y
    (MagicString x) == (MagicInt y) = x == (show y)

I’m sorry to inflict that code on you. Let’s erase it from our minds using existential types. In quest of the Magic recipe, I jumped on #haskell and things got crazy. It’s filled with friendly and alarmingly smart people.

Here’s what I learned. If we leave the Haskell98 standard behind, we can open up a trap door in GHCI by starting it with the -XExistentialQuantification option. This enables existential type extensions.

We can define a constructor for Magic which accepts anything that can be shown, then define equality by comparing string values.

hci> data Magic = forall a. (Show a) => Magic a
ghci> instance Eq Magic where (Magic x) == (Magic y) = show x == show y
ghci> Magic 5 == Magic 5
true
ghci> Magic 5 == Magic "a string"
false
ghci> Magic 5 == Magic "5"
false

The last one is false because show 5 is “5” whereas show “5” is “"5"”. Nonetheless it’s what we wanted.

This really is my first day learning Haskell, so please comment and set me straight if I’m doing things wrong. Also, what is a more realistic use of forall?

comments powered by Disqus