Как я могу улучшить свое понимание системы Haskell Type?
После многократного вмешательства в генератор случайных чисел я пришел к выводу, что мое понимание системы типа 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 = 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)
Итак, я начинаю приходить к выводу, что я действительно не знаю, что делаю. Итак:
- Может кто-нибудь объяснить, что мне здесь не хватает?
- Любые указатели на главу и стих, где я мог бы рискнуть понять основные принципы?
Ответы
Ответ 1
Проблема здесь в том, что 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
. Либо будет работать для этого случая, но компилятор этого не знает.
Кроме того, я бы предложил несколько стилистических изменений, чтобы избавиться от некоторых из этих парнов:
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 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.
Ответ 2
Что касается того, как вы можете больше узнать о том, как отлаживать эту проблему, вот трюк, который очень помог мне. Всякий раз, когда я полностью озадачен сообщением вроде этого, я делаю следующее:
- Если есть подпись типа в рассматриваемой функции, удалите ее и посмотрите, что-нибудь изменится. Если он компилируется, спросите ghci, что это за тип (используя
: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
И, конечно, как только я исправлю ошибку компиляции, я еще раз взгляну на исходное сообщение об ошибке, чтобы узнать, могу ли я это понять сейчас.