如何提高对Haskell Type系统的理解?

Bre*_*ugh 12 haskell types

在对随机数生成器进行了大量修补之后,我得出的结论是,我对Haskell类型系统的理解是不完整的,如果不完全丢失的话.

这是一个例子.我正在尝试生成泊松事件时间流:

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 ) )
Run Code Online (Sandbox Code Playgroud)

因此,我责备我:

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))'
Run Code Online (Sandbox Code Playgroud)

在四处寻找之后,我通过改变最后一行来"修复"它:

   printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen ) :: [Double])
Run Code Online (Sandbox Code Playgroud)

现在,我想使用有限精度算术,所以我将"next"行更改为:

           next = rndp (start - log(rvalue) / rate)
Run Code Online (Sandbox Code Playgroud)

现在它失败了:

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)
Run Code Online (Sandbox Code Playgroud)

所以我开始得出结论,我真的不知道自己在做什么.所以:

  1. 有人可以解释我在这里缺少的东西吗?
  2. 任何指向章节和诗节的指针,我都有机会理解基本原则?

bhe*_*ilr 14

这里的问题是GHC无法自动确定RealFloat您要使用的是哪个.你按照条件对所有内容进行了编码RealFloat,并且在main没有提供具体类型的情况下使用它,因此它停止并说"无法弄明白".您可以通过更改至少一个要使用FloatDouble特定的类型签名来解决此问题,但更好的解决方案是只指定它应该属于哪种类型main,如下所示:

main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double])
Run Code Online (Sandbox Code Playgroud)

当您添加[Double]到此行时,您明确告诉GHC在运行时使用哪种类型.没有它,它只知道使用RealFloat rRandom r,并且有多种类型可供选择,即FloatDouble.这两种情况都适用于这种情况,但编译器不知道这一点.

另外,我会建议一些风格上的改变,以摆脱一些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])
Run Code Online (Sandbox Code Playgroud)

这些变化大多来自hlint.

  • @ Brent.Longborough这主要是因为我将你的代码粘贴在我的编辑器中,并突出显示你可以摆脱没有做任何事情的括号.newline-at-do-block也只是我的烦恼之一,但你的风格并不特别令人反感.至于类型,我会说只有你才能知道你是否需要'RealFloat`的灵活性.如果您希望将它用于"Double"以外的类型,那么请保持原样.如果您只想将它​​与`Double`一起使用,那么只需更改签名即可明确使用`Double`. (3认同)

mhw*_*bat 11

至于你如何能够更多地了解如何调试这类问题,这里有一个非常有帮助的技巧.每当我对这样的消息感到困惑时,我会做以下事情:

  • 如果有问题的功能上有类型签名,请将其删除并查看是否有任何更改.如果它编译,请询问ghci类型是什么(使用:t).如果它没有编译,至少错误消息可能有足够的差异给你另一个线索.
  • 如果没有类型签名,请添加一个.即使它没有编译,错误消息可能会给你另一个线索.
  • 如果这没有帮助,那么暂时在函数中的每个表达式上添加类型声明.(通常你需要拆分一些表达式才能看到真正发生的事情.你可能还需要暂时启用ScopedTypeVariablespragma.)再次编译并检查错误消息.

最后一个是更多的工作,但我从这个练习中学到了很多东西.它通常指出了我认为类型与GHC认为类型之间存在不匹配的确切位置.

如果我在你的代码上做了最后一个,那么更改可能看起来像这样.请注意,错误现在指向main函数而不是printAll函数,这有助于我们找出修复它的位置.

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

main = do
  let temp2 = take 10 (infinitePoissonStream 1.0 0.0 myGen ) :: (RealFloat r) => [r]
  -- but if you make this change, it compiles:
  -- let temp2 = take 10 (infinitePoissonStream 1.0 0.0 myGen ) :: [Double]
  printAll temp2
Run Code Online (Sandbox Code Playgroud)

当然,一旦我修复了编译错误,那么我再看一下原始的错误信息,看看我现在能不能理解它.