12

乱数ジェネレーターをいじくり回した結果、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. 誰かが私がここで見逃していることを説明できますか?
  2. 根底にある原則を理解する可能性がある章と節へのポインタはありますか?
4

2 に答える 2

14

ここでの問題は、GHC がどちらRealFloatを使いたいかを自動的に判断できないことです。の観点からすべてをコーディングしましたがRealFloat、使用する具体的な型を提供しmainていないため、停止して「理解できませんでした」と表示されます。少なくとも 1 つのタイプ シグネチャを使用するように変更するFloatか、具体的に変更することでこれを修正できますが、より良い解決策は、次のようDoubleに、どのタイプに含まれるべきかを指定することです。main

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

この行にを追加する[Double]と、実行時に使用する型をGHCに明示的に伝えます。RealFloat rそれがなければ、 andを使用することしか認識しておらず、Random r選択できる複数のタイプ、つまりFloatandがあり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'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 によるものです。

于 2013-10-08T12:55:47.347 に答える
11

この種の問題をデバッグする方法について詳しく知る方法については、非常に役立つトリックがあります。このようなメッセージに完全に困惑するたびに、私は次のことを行います。

  • 問題の関数に型シグネチャがある場合は、それを削除して、何かが変わるかどうかを確認します。コンパイルできたら、ghci にタイプを尋ねます ( を使用:t)。コンパイルされない場合、少なくともエラー メッセージが異なるため、別の手がかりが得られる可能性があります。
  • 型シグネチャがない場合は、1 つ追加します。コンパイルされない場合でも、エラー メッセージから別の手がかりが得られる場合があります。
  • それでも問題が解決しない場合は、関数内の各式に型宣言を一時的に追加します。ScopedTypeVariables(多くの場合、実際に何が起こっているかを確認するために、いくつかの式を分割する必要があります。プラグマを一時的に有効にする必要がある場合もあります。) 再度コンパイルして、エラー メッセージを確認します。

最後の 1 つはより多くの作業ですが、私はその演習から多くのことを学びました。それは通常、私が考える型と GHC が考える型との間に不一致がある正確な場所を特定します。

あなたのコードで最後の 1 つを行っていた場合、変更は次のようになります。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

もちろん、コンパイル エラーを修正したら、元のエラー メッセージをもう一度見て、理解できるかどうかを確認します。

于 2013-10-08T14:09:26.793 に答える