わかった。型の問題がある場合はいつでも、明示的な型注釈をコンパイラに与えることから始めるのが最善の方法です。とはおそらくあまり大きくないのでday
、にするのがよいでしょう。また、ブレースを見逃したようです。修正しました。month
year
Int
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = floor(275 * fromIntegral month / 9)
n2 = floor((month + 9) / 12)
n3 = 1 + floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)
これをコンパイルしようとすると、GHC はかなり長いエラー メッセージを吐き出します。
bar.hs:8:16:
(RealFrac Int) のインスタンスがありません
「床」の使用から生じる
可能な修正: (RealFrac Int) のインスタンス宣言を追加します。
`(+)' の 2 番目の引数では、つまり
`floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
式では:
1 + フロア ((年 - 4 * フロア (整数年 / 4) + 2) / 3)
「n3」の式では、次のようになります。
n3 = 1 + フロア ((年 - 4 * フロア (整数年 / 4) + 2) / 3)
バー.hs:8:68:
(Fractional Int) のインスタンスがありません
`/' の使用から生じる
可能な修正: (Fractional Int) のインスタンス宣言を追加します。
「floor」の最初の引数では、つまり
`((年 - 4 * 床 (fromIntegral 年 / 4) + 2) / 3)'
`(+)' の 2 番目の引数では、つまり
`floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
式では:
1 + フロア ((年 - 4 * フロア (整数年 / 4) + 2) / 3)
2 番目のエラーは重要なエラーで、最初のエラーはフォローアップです。それは本質的に言います:Int
除算を実装していませんfloor
。Haskell では、整数除算は別の関数 (div
またはquot
) を使用しますが、ここでは浮動小数点除算が必要です。year
が に固定されているため、Int
減数4 * floor(fromIntegral year / 4) + 2
も に固定されていますInt
。次に 3 で割りますが、前に述べたように浮動小数点は使用できません。fromIntegral
分割する前に、用語全体を別の型に「キャスト」して修正しましょう(以前に行ったように)。
fromIntegral
署名があります(Integral a, Num b) => a -> b
。つまりfromIntegral
、整数型 (Int
または などInteger
) の変数を受け取り、任意の数値型の変数を返します。
更新されたコードをコンパイルしてみましょう。の定義にも同様のエラーが表示されn2
ます。同様に修正しました。
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = floor(275 * fromIntegral month / 9)
n2 = floor((fromIntegral month + 9) / 12)
n3 = 1 + floor(fromIntegral (year - 4 * floor(fromIntegral year / 4) + 2) / 3)
このコードはコンパイルして正常に実行されます (私のマシン上で)。Haskell には特定の型デフォルト規則があり、コンパイラDouble
はすべての浮動小数点除算の型として選択します。
実際、あなたはそれよりもうまくやることができます。浮動小数点変換を繰り返す代わりに、整数除算を使用するのはどうですか?
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = 275 * month `quot` 9
n2 = (month + 9) `quot` 12
n3 = 1 + (year - 4 * (year `quot` 4) + 2) `quot` 3
このアルゴリズムは、常に上記の浮動小数点バージョンと同じ結果を生成する必要があります。たぶん10倍くらい速いです。バックティックを使用すると、関数 ( quot
) を演算子として使用できます。
6 番目のポイントについて: はい、とても簡単にできます。とfromEnum
の前year
にa を付けるだけです。この関数は、列挙型を に変換します。Haskell で使用可能なすべての数値型 (複雑な iirc を除く) は、 class のメンバーです。ただし、通常は引数があり、余分な関数呼び出しがプログラムの速度を低下させる可能性があるため、これはあまり良い考えではありません。関数が多くの異なる型で使用されることが予想される場合を除いて、明示的に変換することをお勧めします。実際には、マイクロ最適化についてあまり心配する必要はありません。ghc には複雑で難解な最適化インフラストラクチャがあり、ほとんどのプログラムが非常に高速になります。month
day
fromEnum :: Enum a => a -> Int
Int
Enum
Int
修正
フォローアップ 1、2、3
はい、あなたの推論はほぼ正しいです。
フォローアップ 4
型シグネチャの浮動小数点バリアントを指定しない場合day_of_year
、その型はデフォルトで になりday_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
ます。これは本質的に次のことを意味します: day
、型クラスを実装する任意の型にすることができmonth
ます。関数は と同じ型の値を返します。この場合、、とは型変数が異なるだけです。そうです、Haskell には型レベルの変数もあります (また、[型の型である] 種類レベルの変数もありますが、それは別の話です)。どの型でも満足できます。 . だからあなたが持っているならyear
Integral
day
a
a1
a2
day_of_year (2012 :: Int16) (5 :: Int8) (1 :: Integer)
変数a
は にインスタンス化されInt16
、 にa1
なりInt8
、 にa2
なりInteger
ます。では、この場合の戻り値の型は何ですか?
それInteger
は、タイプ署名を見てください!
フォローアップ 5
実際、あなたは同時にそうであり、そうではありません。型を可能な限り一般的なものにすることには確かに利点がありますが、型チェッカーを混乱させます。明示的な型注釈のない項に含まれる型が一般的すぎる場合、コンパイラは複数の可能性があることを発見する可能性があります用語を入力します。これにより、コンパイラは、多少直感的ではありませんが、標準化された規則によって型を選択するか、単に奇妙なエラーが表示される可能性があります。
本当に一般的なタイプが必要な場合は、次のようなものを目指してください
day_of_year :: Integral a => a -> a -> a -> a
つまり、引数は任意のIntegral
型にすることができますが、すべての引数は同じ型でなければなりません。
Haskellは型をキャストしないことを常に覚えておいてください。(自動) キャストが関係している場合、型を完全に推論することはほとんど不可能です。手動でキャストするだけです。一部の人々は、タイプが でunsafeCoerce
ある module 内の関数について教えてくれるかもしれませんが、実際には知りたくありません。それはおそらくあなたが思っていることをしません。Unsafe.Coerce
a -> b
フォローアップ 6
に問題はありませんdiv
。負の数が含まれる場合、違いが現れ始めます。最新のプロセッサ (Intel、AMD、および ARM 製のものなど) は、ハードウェアに実装さquot
れrem
ています。div
もこれらの操作を使用しますが、別の動作を取得するためにいくつかの操作を行います。負の数に関する正確な動作に実際に依存していない場合、計算が不必要に遅くなります。(実際には、ハードウェアでdiv
はなく実装しているマシンがいくつかありquot
ます。私が今覚えているのはmmixだけです)