13

私はこのJavascriptコードを持っています:

N1 = Math.floor(275 * month / 9)
N2 = Math.floor((month + 9) / 12)
N3 = (1 + Math.floor((year - 4 * Math.floor(year / 4) + 2) / 3))
N = N1 - (N2 * N3) + day - 30
return N

私はそれをHaskellに移植しようとしました。このような:

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)

それは動作しません:(
ここに私の質問があります:

  1. なぜタイプはのよう にn1書かれているn1 :: (Integral b, RealFrac a) => a -> b
    のにそうではないのですかn1 :: (RealFrac a, Integral b) => a -> b
    floor :: (Integral b, RealFrac a) => a -> b

    回答:=>の左側の順序は重要ではありません
    。ghciは通常、宣言の順序と同じ順序を維持しようとしますが、
    デフォルトでabcの順序になる場合があります。

  2. このステートメントは正しいですか:n1整数を取り、RealFracを返します。

    回答:はい。=>の左側で順序付けが重要でないことがわかっている場合は
    、(Integral b、RealFrac a)===(RealFrac a、Integral b)
    重要なのはタイプa- >b
    またはこの場合はIntegral-であることもわかります。 > RealFrac

  3. n3単相性の病気があります。どうすれば治すことができますか?
    私はこのfを機能させるだけでなく、全体像に興味があります。私はモノについて読んだことがあります...しかし、どこに置くべきかわかりません::この場合:(

    回答:ここには単相性はありません。FUZxxlの答えを見てください:)

  4. このようにすることができday_of_yearます:Integral -> Integral -> Integral -> Integral
    3つの積分を取り、積分結果を返します。

    回答:はい、できます!
    ::整数a=>a-> a-> a-> a :: Int-
    > Int-> Int->-> Int
    ::(Integral a、Integral a2、Integral a1)=>a- > a1-> a2-> a2

  5. 私はday_of_year3つの整数または3つの整数しか取ることができないと思います。2 Ints1integerのようなミックスはできませんでした。右?

    FUZxxl:いいえ、さまざまな引数タイプが混在する可能性があります。フォローアップ4を見てください!!!

  6. day_of_year3つのNumを取り、Numを返すように作成することは可能ですか?

    FUZxxl:はい、そうです!年、月、日の前にfromEnumを配置します

4

2 に答える 2

16

わかった。型の問題がある場合はいつでも、明示的な型注釈をコンパイラに与えることから始めるのが最善の方法です。とはおそらくあまり大きくないのでday、にするのがよいでしょう。また、ブレースを見逃したようです。修正しました。monthyearInt

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 には複雑で難解な最適化インフラストラクチャがあり、ほとんどのプログラムが非常に高速になります。monthdayfromEnum :: Enum a => a -> IntIntEnumInt

修正

フォローアップ 1、2、3

はい、あなたの推論はほぼ正しいです。

フォローアップ 4

型シグネチャの浮動小数点バリアントを指定しない場合day_of_year、その型はデフォルトで になりday_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2ます。これは本質的に次のことを意味します: day、型クラスを実装する任意の型にすることができmonthます。関数は と同じ型の値を返します。この場合、、とは型変数が異なるだけです。そうです、Haskell には型レベルの変数もあります (また、[型の型である] 種類レベルの変数もありますが、それは別の話です)。どの型でも満足できます。 . だからあなたが持っているならyearIntegraldayaa1a2

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.Coercea -> b

フォローアップ 6

に問題はありませんdiv。負の数が含まれる場合、違いが現れ始めます。最新のプロセッサ (Intel、AMD、および ARM 製のものなど) は、ハードウェアに実装さquotremています。divもこれらの操作を使用しますが、別の動作を取得するためにいくつかの操作を行います。負の数に関する正確な動作に実際に依存していない場合、計算が不必要に遅くなります。(実際には、ハードウェアでdivはなく実装しているマシンがいくつかありquotます。私が今覚えているのはです)

于 2012-06-27T18:06:07.533 に答える
0

簡単なコメントをするには、フォローアップの質問が多すぎます。

  1. n1明らかです。fromIntegralを取りmonth、それをの必要なタイプにキャストし/ます。
    私はここにいますか?

    はい。

  2. しかし、私たちは===n2と仮定することができます
    fromIntegral(month + 9)(fromIntegral month + 9)

    • 最初のケースではmonth、と9が追加されてから、あるタイプにキャストされます。/ これ+は、にあるために機能するため、すべての数値をキャストせずに使用Numできます。+そして、のような生の数字1,2,3Numタイプです。
    • 2番目のケースには、ある種の「遅延キャスティング」があります。(fromIntegral month + 9)タイプNum a => aがありますが、/12コンパイラがmonthAND9と互換性のあるタイプにキャストするためです/
      私はこれを正しく理解しましたか?

      !はい。

  3. FUZxxl、男、ありがとう!
    私は、コードをいじってfromIntegralランダムに配置することで、これを解決することに非常に近かった。
    しかし、コードを機能させることは、なぜ私たちが何かをしているのかを知ることと同じではありません!

    • floorANDの両方/は許可されていませんIntegral
    • n3、2番目のyear変数:を使用することにより、使用できるfloor(fromIntegral year / 4)結果を偶然に作成しましたfloor。そして、その表現は全体
      (year - 4 * floor(fromIntegral year / 4) + 2)を型クラスにしIntegralました!したがって、最初に機能
      することを不可能にします。 私の論理は大丈夫ですか?/3floor

      !はい。

  4. あなたのタイピングは機能します: day_of_year :: Int -> Int -> Int -> Int
    私のタイピングも機能します:day_of_year :: Integral a => a -> a -> a -> a
    自動タイピング:day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
    これはどういう意味ですか?a1、a2とは何ですか?なぜa、a1、a2?

    フォローアップ4の素晴らしい答え

  5. Integral特定の関数の代わりに取る一般的な関数を作成しようとして、ここで間違いを犯していますIntInteger

    • JavaScriptでは、すべてが自動キャストされ、typeに動的に型付けされNumberます。
    • C ++にはテンプレートがあるため、ジェネリック関数は多くのタイプで機能します。

      !フォローアップ5を見てください

  6. なぜquot代わりに使用したのdivですか?
    昨日、私はあなたがしたことを正確に試し、私divghci 報酬を与えました:
    No instance for (Integral (Car -> Int)) arising from a use of div

    • このエラーは何ですか?
    • この場合のdivとquotの違いは何ですか?

      !関数定義から誤って年月を消去してしまいまし
      !day_of_year day = ...
      !そしてそのエラーが現れました。

ps Google:haskellの結果が見つかりません "(Integral(Car-> Int))"

Googleでさえ私のタイプミスを見つけることができません;)))

于 2012-06-28T05:04:28.167 に答える