经过对随机数生成器的大量修改后,我得出的结论是,如果不完全丢失,我对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 ) )
因此,我责备我:
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))'
在探索之后,我通过改变最后一行来“修复”它:
printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen ) :: [Double])
现在,我想使用有限精度算术,所以我将“next”行更改为:
next = rndp (start - log(rvalue) / rate)
现在它失败了:
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)
所以我开始得出结论,我真的不知道我在做什么。所以:
答案 0 :(得分:14)
这里的问题是GHC无法自动确定您要使用的RealFloat
。您使用RealFloat
对所有内容进行编码,而在main
中,您没有提供具体类型供其使用,因此它会停止并说“无法弄明白”。您可以通过更改至少一个类型签名来具体使用Float
或Double
来解决此问题,但更好的解决方案是仅指定main
中应该使用的类型,像这样:
main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double])
将[Double]
添加到此行时,您明确告诉GHC在运行时使用哪种类型。如果没有它,它只知道使用RealFloat r
和Random r
,并且有多种类型可供选择,即Float
和Double
。这两种情况都适用于这种情况,但编译器不知道。
此外,我会建议一些风格上的改变,以摆脱一些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])
这些变化主要来自hlint。
答案 1 :(得分:11)
至于你如何能够更多地了解如何调试这类问题,这里的诀窍对我帮助很大。每当我对这样的消息感到困惑时,我会做以下事情:
:t
)。如果它没有编译,至少错误消息可能有足够的差异给你另一条线索。ScopedTypeVariables
编译指示。)再次编译并检查错误消息。最后一个是更多的工作,但我从这个练习中学到了很多东西。它通常会指出我认为类型与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
当然,一旦我修复了编译错误,那么我再看一下原始的错误信息,看看我现在能不能理解它。