Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I improve my understanding of the Haskell Type system?

Tags:

types

haskell

After a lot of tinkering at a random number generator, I have come to the conclusion that my understanding of the Haskell type system is incomplete, if not missing altogether.

Here's an example. I'm trying to generate a stream of Poisson event times:

import System.Random
import Numeric

bround :: (RealFloat r, Integral b) => b -> r -> r
bround places x = (fromIntegral (round ( x * exp))) / exp
       where exp = 10.0 ^ places

rndp = (bround 4)

myGen = (mkStdGen 1278267)

infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => r -> r -> g -> [r]
infinitePoissonStream rate start gen = next:(infinitePoissonStream rate next newGen)
        where  (rvalue, newGen) = random gen
               next = (start - log(rvalue) / rate)

printAll :: (RealFloat r) => [r] -> IO ()
printAll []     = return ()
printAll (x:xs) = do putStrLn (showFFloat (Just 8) x "")
                     printAll xs

main = do
       printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen ) )

which chides me thus:

mwe3.hs:23:8:
    No instance for (RealFloat r0) arising from a use of `printAll'
    The type variable `r0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance RealFloat Double -- Defined in `GHC.Float'
      instance RealFloat Float -- Defined in `GHC.Float'
      instance RealFloat Foreign.C.Types.CDouble
        -- Defined in `Foreign.C.Types'
      ...plus one other
    In a stmt of a 'do' block:
      printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen))
    In the expression:
      do { printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) }
    In an equation for `main':
        main
          = do { printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) }

mwe3.hs:23:27:
    No instance for (Random r0)
      arising from a use of `infinitePoissonStream'
    The type variable `r0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance Random Bool -- Defined in `System.Random'
      instance Random Foreign.C.Types.CChar -- Defined in `System.Random'
      instance Random Foreign.C.Types.CDouble
        -- Defined in `System.Random'
      ...plus 33 others
    In the second argument of `take', namely
      `(infinitePoissonStream 1.0 0.0 myGen)'
    In the first argument of `printAll', namely
      `(take 10 (infinitePoissonStream 1.0 0.0 myGen))'
    In a stmt of a 'do' block:
      printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen))

mwe3.hs:23:49:
    No instance for (Fractional r0) arising from the literal `1.0'
    The type variable `r0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance Fractional Double -- Defined in `GHC.Float'
      instance Fractional Float -- Defined in `GHC.Float'
      instance Integral a => Fractional (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus two others
    In the first argument of `infinitePoissonStream', namely `1.0'
    In the second argument of `take', namely
      `(infinitePoissonStream 1.0 0.0 myGen)'
    In the first argument of `printAll', namely
      `(take 10 (infinitePoissonStream 1.0 0.0 myGen))'

After poking around, I "fixed" it by changing the last line:

   printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen ) :: [Double])

Now, I wanted to use limited-precision arithmetic, so I changed the "next" line to this:

           next = rndp (start - log(rvalue) / rate)

and now it fails thus:

mwe3.hs:15:29:
    Could not deduce (r ~ Double)
    from the context (RandomGen g, Random r, RealFloat r)
      bound by the type signature for
                 infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) =>
                                          r -> r -> g -> [r]
      at mwe3.hs:12:26-83
      `r' is a rigid type variable bound by
          the type signature for
            infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) =>
                                     r -> r -> g -> [r]
          at mwe3.hs:12:26
    In the first argument of `(-)', namely `start'
    In the first argument of `rndp', namely
      `(start - log (rvalue) / rate)'
    In the expression: rndp (start - log (rvalue) / rate)

So I'm beginning to come to the conclusion that I really don't know what I'm doing. So:

  1. Can someone explain what I'm missing here?
  2. Any pointers to chapter and verse where I might stand a chance of understanding the underlying principles?
like image 698
Brent.Longborough Avatar asked Oct 08 '13 12:10

Brent.Longborough


People also ask

What makes Haskell so special?

One of the things that makes Haskell unique is its strong, static type system. Understanding this system is one of the keys to understanding Haskell. It is radically different from the dynamic typing of languages like Python, Javascript, and Ruby.

How long does it take to learn Haskell?

Surprisingly, having experienced object-oriented programming can slow you down when trying to learn Haskell, as your mind is accustomed to the way objects and classes help build applications. All in all, you can expect to devote four to six hours daily for a period of four to six weeks to get a good grip on the language.

What are the types of expressions in Haskell?

Every expression in Haskell has a type, including functions and if statements The compiler can usually infer the types of expressions, but you should generally write out the type signature for top level functions and expressions.


1 Answers

The problem here is that GHC can not automatically figure out which RealFloat you want to use. You coded everything in terms of RealFloat, and in main you haven't provided a concrete type for it to use, so it stops and says "couldn't figure it out". You can fix this by either changing at least one of your type signatures to use Float or Double specifically, but the better solution is to just specify which type it's supposed to be in main, like so:

main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double])

When you add the [Double] to this line, you are explicitly telling GHC which type to use during runtime. Without that, it only knows to use RealFloat r and Random r, and there are multiple types to choose from, namely Float and Double. Either will work for this case, but the compiler doesn't know that.

Additionally, I would suggest a few stylistic changes to get rid of some of those parens:

import System.Random
import Numeric

bround :: (RealFloat r, Integral b) => b -> r -> r
bround places x = fromIntegral (round $ x * e) / e
       where e = 10.0 ^ places
       -- exp is a pre-defined function, shouldn't name a variable with it

-- Even if it's trivial, you should add type signatures, it really helps others read your code faster
rndp = bround 4
myGen = mkStdGen 1278267

-- function application before operator application means you can remove some parens
infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => r -> r -> g -> [r]
infinitePoissonStream rate start gen = next : infinitePoissonStream rate next newGen
        where  (rvalue, newGen) = random gen
               next = start - log rvalue / rate

-- Start a new line at the beginning of a do block, the indentations are nicer
printAll :: (RealFloat r) => [r] -> IO ()
printAll []     = return ()
printAll (x:xs) = do
    putStrLn $ showFFloat (Just 8) x ""
    printAll xs

-- No need for a do block with only one statement
main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double])

These changes mostly from hlint.

like image 69
bheklilr Avatar answered Oct 22 '22 22:10

bheklilr