Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type System in Haskell

Tags:

haskell

I am having trouble understanding why the following code results in an error. If the second field of Foo is changed to type Int, the code runs without errors.

Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> (show . fn) x) [foo_a, foo_b]

<interactive>:4:46:
    Couldn't match type `Float' with `Int'
    Expected type: Foo -> Int
      Actual type: Foo -> Float
    In the expression: foo_b
    In the second argument of `map', namely `[foo_a, foo_b]'
    In the second argument of `($)', namely
      `map (\ fn -> (show . fn) x) [foo_a, foo_b]'

Why is show unable to accept arguments of varying types? The following, of course, work:

Prelude> show $ foo_a x
"2"
Prelude> show $ foo_b x
"3.4"

Also, given that this doesn't work, what is the recommended way to apply show to various fields of a data type?

Thanks.

like image 938
akn320 Avatar asked Aug 14 '15 05:08

akn320


2 Answers

The problem is that lists in Haskell are homogeneous (all the elements have the same type). And in [foo_a, foo_b] you are trying to create a list with two different types: Foo -> Int and Foo -> Float.

One solution is to move the show inside the list and create a list of Foo -> String:

Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> fn x) [show . foo_a, show . foo_b]
"2 3.4"

((\fn -> fn x) can be written as ($ x))


Another possibility is to create a data type to unify the the several types you want to put in a list, mimicking a heterogeneous collection. In your case, it could be something like this:

{-# LANGUAGE ExistentialQuantification #-}

data Showable b = forall a . Show a => MkShowable (b -> a)

pack :: Show a => (b -> a) -> Showable b
pack = MkShowable

unpack :: Showable b -> b -> String
unpack (MkShowable f) = show . f

Then, you can do:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"

[Update]

I was playing around with the Data.Dynamic, and it seems more promising than the existential type I created above.

Let's start with:

{-# LANGUAGE DeriveDataTypeable #-}

import Control.Applicative
import Data.Dynamic
import Data.Maybe

data Foo = Foo {foo_a :: Int, foo_b :: Float}
  deriving Typeable

get_a :: Dynamic -> Maybe (Foo -> Int)
get_a = fromDynamic

get_b :: Dynamic -> Maybe (Foo -> Float)
get_b = fromDynamic

getAsString :: Dynamic -> (Foo -> String)
getAsString dyn = fromJust $  (show .) <$> get_a dyn
                          <|> (show .) <$> get_b dyn
                          <|> error "Type mismatch"

In this case, we can do:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"

It seems that we had to write more code to achieve the same result, but it actually gives us much more flexibility. While the existential type was only focused in showing the fields as String, here we are not limited to that. What if now we want to see all the fields as Int (in the case of Float we want to round the value)?

getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $                get_a dyn
                       <|> (round .) <$> get_b dyn
                       <|> error "Type mismatch"

Now we can do:

*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]
like image 115
Helder Pereira Avatar answered Oct 31 '22 01:10

Helder Pereira


This is a limitation of the Hindley-Milner type system. Only definitions in let (and equivalently, in where and at the top level) can be polymorphic. In particular, arguments to functions cannot be polymorphic. fn must either have type Foo -> Int or Foo -> Float -- there is no Foo -> (Int or Float) or Foo -> Showable type.

If you must, you can define a Showable type, which is known as an existential type, but you have to give the typechecker some help in order to use it, and in most code the idea is not used because its inconvenience outweighs its usefulness, and we generally have no trouble expressing what we want without it.

like image 30
luqui Avatar answered Oct 31 '22 02:10

luqui