Haskell / GHCの `forall`キーワードは何をしますか?


312

次のように、forallいわゆる「既存のタイプ」でキーワードがどのように使用されるかを理解し始めています。

data ShowBox = forall s. Show s => SB s

これは、forall使用方法のサブセットにすぎませんが、次のようなもので使用することに心を奪うことはできません。

runST :: forall a. (forall s. ST s a) -> a

または、これらが異なる理由を説明します。

foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

または全部RankNTypes...

私は、学術的な環境では普通の種類の言語よりも、明確で専門用語のない英語を好む傾向があります。私がこれについて読んでみようとするほとんどの説明(検索エンジンで見つけることができるもの)には、次の問題があります。

  1. 彼らは不完全です。彼らは、私は、コードを読むまで、私は幸せな気分になります(「実存的なタイプ」のように)このキーワードの使用の一部を説明することは完全に異なる方法での使用にそれを(のようなrunSTfooおよびbar上記)。
  2. それらは、離散数学、カテゴリー理論、または抽象代数の今週で人気のある分岐の最新のものを読んだという仮定が密集しています。(「実装の詳細については、何でも紙に相談してください」という言葉を二度と読んだことがなければ、早すぎるでしょう。)
  3. 彼らは頻繁に単純な概念でさえも曲がりくねって壊れた文法と意味論に変える方法で書かれています。

そう...

実際の質問に移ります。forall私が専門用語に染まっている数学者であるとは思わない誰かが、キーワードを明確でわかりやすい英語で完全に説明できますか(または、どこかに存在する場合、見逃した明確な説明を指摘します)。


追加するように編集:

以下のより高品質なものから2つの傑出した答えがありましたが、残念ながら私は最良のものとして1つしか選択できません。 ノーマンの答えは詳細で有用であり、理論的な土台のいくつかを示しforall、同時にそれの実際的な影響のいくつかを私に示した方法で物事を説明しました。 ヤルチュの答え他には誰も言及していない領域(スコープタイプ変数)をカバーし、コードとGHCiセッションですべての概念を説明しました。両方を最良のものとして選択することができたとしたら、私はそうします。残念ながら私はできません。両方の答えを注意深く検討した結果、コード例と添付の説明のために、yairchuはNormanのそれよりもわずかに縁がないと判断しました。しかし、これは少し不公平です。なぜなら、forall型シグネチャでそれを見ると、ほんの少しの恐怖感を私に残さないように、これを理解するには両方の答えが必要だったからです。


7
Haskell wikiは、このトピックに関してかなり初心者にやさしいようです。
jhegedus 14年

回答:


263

コード例から始めましょう:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

このコードは、プレーンなHaskell 98ではコンパイル(構文エラー)しませんforall。キーワードをサポートするには、拡張機能が必要です。

基本的には、そこに3つのある別の共通の用途forallキーワード(あるいは少なくともそれはそうと思われるが)、それぞれが独自のHaskellの拡張子を持っていますScopedTypeVariablesRankNTypes/ Rank2TypesExistentialQuantification

上記のコードは、どちらかを有効にしても構文エラーは発生しませんが、有効にした場合の型チェックのみがScopedTypeVariables可能です。

スコープ型変数:

スコープ型変数は、where句内のコードの型を指定するのに役立ちます。それは作るbにはval :: b同じものbの中でfoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b

紛らわしい点forallタイプからを省略しても、実際には暗黙的にそこにあると聞いているかもしれません。(ノーマンの答えから:「通常これらの言語は多相型からforallを省略します」)。この主張は正しいですが、それはの他の使用法に言及しており、その使用法には言及してforallいませんScopedTypeVariables

ランクNタイプ:

レッツ・スタートmayb :: b -> (a -> b) -> Maybe a -> bに相当しmayb :: forall a b. b -> (a -> b) -> Maybe a -> b除いたときのためにScopedTypeVariables有効になります。

これは、すべてのaおよびで機能することを意味しbます。

このようなことをしたいとしましょう。

ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

これのタイプは何liftTupですか?ですliftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)。理由を確認するために、コードを作成してみましょう。

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

「うーん。なぜGHCはタプルに同じタイプの2つが含まれている必要があると推測するのでしょうか。そうである必要はないと言ってみましょう。」

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

うーん。ので、ここでGHCは、私たちが適用できていないliftFuncvためにv :: bliftFunc望んでいますx。関数には、可能な限り受け入れる関数を取得してもらいたいのですx

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

だから、それliftTupはすべてのために働くわけではありませんx、それがするのはそれが得る機能です。

存在量:

例を使用してみましょう:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

ランクNタイプとどう違うのですか?

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

ランクNタイプとforall aは、式がすべての可能なasに適合する必要があることを意味します。例えば:

ghci> length ([] :: forall a. [a])
0

空のリストは、どのタイプのリストとしても機能します。

だから、実存-定量とforallによdata定義はそれを意味する、含まれている値が可能であることがどんなことがないことを、適したタイプしなければならないのも、すべての適切なタイプ。


わかりました。6時間ありました。これで解答を解読できます。:)あなたとノーマンの間で、私が探していたまさにそのような答えを得ました。ありがとう。
ちょうど私の正しい意見

2
実際には、あなたScopedTypeVariablesはそれがそれよりも悪いように見えます。b -> (a -> b) -> Maybe a -> bこの拡張子が付いたタイプを記述しても、とまったく同じになりforall a b. b -> (a -> b) -> Maybe a -> bます。あなたはを参照したい場合は、同じ b(と、それは暗黙的に定量化していない)、その後、あなたが明示的に定量化されたバージョンを作成する必要があります。そうでSTVなければ、非常に煩わしい拡張になります。
nominolo

1
@nominolo:私は中傷するつもりScopedTypeVariablesはなかったし、それが悪いとは思わない。imhoそれはプログラミングプロセス、特にHaskellの初心者にとって非常に役立つツールであり、それが存在していることに感謝しています。
yairchu

2
これはかなり古い質問(および回答)ですが、GADTを使用して(少なくとも私にとっては)数量化をはるかに理解しやすくする方法で実存型を表現できるという事実を反映するように更新する価値があります。
dfeuer

1
私は個人的に、GADTフォームへの変換という観点から存在表記を説明すること/理解することは、それ自体よりも簡単であると思いますが、他の方法で考えることは確かです。
dfeuer 2015

117

誰かがforallキーワードを明確でわかりやすい英語で完全に説明できますか?

いいえ(まあ、たぶんドン・スチュワートはできるでしょう。)

ここに、簡単で明確な説明、またはforall

  • それは数量詞です。普遍的または実存的な量指定子を見るためには、少なくとも少しのロジック(述語計算)が必要です。述語計算を見たことがない、または数量詞に慣れていない場合(そして私がPhD資格試験中に不快な学生を見たことがあります)、あなたにとって、の簡単な説明はありませんforall

  • これは型の数量詞です。システムFを見たことがなく、ポリモーフィック型を書く練習をしていると、forall混乱するでしょう。HaskellやMLの経験は十分ではありませんforall。これらの言語は通常、多相型から除外されるためです。(私の考えでは、これは言語設計の間違いです。)

  • 特にHaskellでは、forall混乱を招くような方法で使用されています。(私は型理論家ではありませんが、私の仕事は多くの型理論に触れさせてくれますし、それにとても慣れています。)私にとって、混乱の主な原因はforall、私自身はで書くことを好みexistsます。これは、量指定子と矢印を含む微妙な同型の同型性によって正当化されます。そして、それを理解したいときはいつも、自分で調べて同型性を自分で調べなければなりません。

    型同型の概念に慣れていない場合、または型同型について考えたことがない場合は、このの使用によって問題forallが生じます。

  • の一般的な概念forallは常に同じ(型変数を導入するためのバインディング)ですが、使用方法の詳細は大幅に異なる場合があります。非公式な英語は、バリエーションを説明するための非常に良いツールではありません。何が起こっているのかを本当に理解するには、いくつかの数学が必要です。この場合、関連する数学はベンジャミンピアスの紹介文であるタイプとプログラミング言語で見つけることができます。これは非常に優れた本です。

あなたの特定の例については、

  • runST 頭が痛くなるはずです。より高いランクのタイプ(矢印の左側)は、野生ではほとんど見られません。私が導入された紙を読むことをお勧めしますrunST「レイジー機能状態のスレッドを」。これは非常に優れた論文でありrunST、特に特定のタイプや一般的に上位のタイプの直感をよりよく提供します。説明は数ページありますが、とてもよくできているので、ここでは要約しません。

  • 検討する

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))

    を呼び出すとbara好きなタイプを選択するだけで、関数をタイプからタイプaに渡すことができaます。たとえば、関数(+1)または関数を渡すことができますreverseforall「今すぐタイプを選べる」と言ってもいいでしょう。(タイプを選択するための専門用語はインスタンス化です。)

    呼び出しに関する制限fooははるかに厳しく、への引数は多態性関数でfoo なければなりません。そのタイプの場合、渡すことfooができる関数はid、のように常に発散するかエラーになる関数のみundefinedです。その理由は、とのことですfooforallそうの呼び出し元として、矢印の左側にあるfooI何を選ぶために得ることはありませんaで、むしろ、それはだな実装fooものを選ぶことを得ることaです。のforallように矢印の上ではなく、矢印の左側にあるためbar、インスタンス化は呼び出しサイトではなく関数の本体で行われます。

概要:完全な説明forallキーワード数学を必要とするだけで数学を勉強した人によって理解することができます。部分的な説明でさえ、数学なしでは理解するのは難しいです。しかし、多分私の数学以外の説明が少し役立つかもしれません。LaunchburyとPeyton Jonesを読んでくださいrunST


補遺:用語の「上」、「下」、「左」。これらは、型が書かれているテキストの方法や、抽象構文木とは何の関係もありません。抽象構文では、a forallは型変数の名前を取り、次にforallの「下」に完全な型があります。矢印は2つのタイプ(引数と結果タイプ)を取り、新しいタイプ(関数タイプ)を形成します。引数のタイプは矢印の「左側」です。これは、抽象構文ツリーの矢印の左の子です。

例:

  • ではforall a . [a] -> [a]、forallは矢印の上にあります。矢印の左側は[a]です。

  • forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f

    括弧内のタイプは、「矢印の左のforall」と呼ばれます。(私が取り組んでいるオプティマイザでこのような型を使用しています。)


実際、私はそれを考えずに上/下/左側に行きました。私はダラーです、はい、しかし以前にそのようなものと格闘しなければならなかった古いダラード。(とりわけASN.1コンパイラを作成します。;)ただし、補遺をありがとう。
私の正しい意見だけ

12
@ちょうど感謝しますが、私は後世のために書いています。私はforall a . [a] -> [a]、forallが矢印の左側にあると考えるプログラマに複数遭遇しました。
ノーマンラムジー

わかりました。詳しくお答えします。今、ノーマンに心から感謝しなければなりません。大きなクリックでたくさんのものが落ちてしまい、まだ理解できないことは少なくとも理解するつもりはないことを認識しておりforall、そのような状況では効果的に、ノイズ。あなたがリンクしているその論文を調べ(リンクもありがとう!)、それが私の理解の領域にあるかどうかを確認します。称賛。
私の正しい意見だけ正しい

10
私は左を読み、文字通り、左を見ました。したがって、「解析ツリー」と言うまでは、私には非常に不明瞭でした。
ポールネイサン

ピアースの本へのポインタのおかげで。それはシステムFの非常に明確な説明を持っています。それはなぜexists実装されなかったのかを説明しています。(それはシステムFの一部ではありません!)Haskellでは、システムFの一部が暗黙的にforallされていますが、ラグの下で完全に掃引できないものの1つです。それはまるで、forall暗黙的にできるHindley-Milnerで始まり、より強力な型システムを選択したかのようであり、FOLの「forall」と「exists」を研究してそこで停止した私たちを混乱させました。
T_S_ 2014

50

私の元の答え:

誰でもforallキーワードを明確でわかりやすい英語で完全に説明できますか

ノーマンが示すように、型理論からの専門用語の明確でわかりやすい英語の説明を与えることは非常に困難です。でも、私たちはみんな努力しています。

'forall'について覚えておかなければならないことが1つだけありますそれは、型をいくつかのスコープにバインドします。それを理解すれば、すべてはかなり簡単です。これは、型レベルでの「lambda」(または「let」の形式)に相当します。NormanRamseyは、「左」/「上」という概念を使用して、スコープのこの同じ概念を優れた回答で伝えています

「forall」のほとんどの使用法は非常に単純であり、GHCユーザーズマニュアルS7.8で紹介されています。特に「forall」のネストされた形式に関する優れたS7.8.5です。

Haskellでは、次のようにタイプが普遍的に修飾されている場合、通常はタイプのバインダーを省略します。

length :: forall a. [a] -> Int

以下と同等です。

length :: [a] -> Int

それでおしまい。

タイプ変数をいくつかのスコープにバインドできるので、最初の例のように、トップレベル(「ユニバーサルに数量化」)以外のスコープを持つことができます。タイプ変数はデータ構造内でのみ表示されます。これにより、非表示のタイプ(「存在タイプ」)が可能になります。または、バインディングを任意にネストすることもできます(「N型のランク付け」)。

型システムを深く理解するには、いくつかの専門用語を学ぶ必要があります。それがコンピュータサイエンスの性質です。ただし、上記のような単純な使用法は、値レベルの「let」との類推によって、直感的に把握できるはずです。すばらしい紹介はLaunchburyとPeyton Jonesです。


5
技術的にlength :: forall a. [a] -> Intlength :: [a] -> IntScopedTypeVariablesが有効になっている場合と同等ではありません。ときforall a.があり、それが影響するlengthwhere(ある場合)句と名前付きの型変数の意味は変化しa、それには。
yairchu

2
確かに。ScopedTypeVariablesはストーリーを少し複雑にします。
ドンスチュワート

3
@DonStewart、あなたの説明では、「型の変数をあるスコープにバインドする」という表現は、「型の変数をあるスコープにバインドする」と表現した方がいいのではないでしょうか。
ロミルド2013

31

今週は、離散数学、カテゴリー理論、または抽象代数が人気のある分野で最新のものを読んだという仮定が密集しています。(「実装の詳細については、何でも紙に相談してください」という言葉を二度と読んだことがなければ、早すぎるでしょう。)

えっと、単純な一次論理についてはどうですか?forallこれは、普遍的な数量化に関連してかなり明確であり、そのコンテキストでは、存在という用語もより意味がありますが、existsキーワードがあった方が不便ではありません。数量化が効果的に普遍的であるか存在的であるかは、変数が関数矢印のどちら側で使用されているかを基準にした量指定子の配置に依存し、少し混乱します。

したがって、それが役に立たない場合、またはシンボリックロジックが気に入らない場合は、より機能的なプログラミングのような観点から、型変数を関数の(暗黙の)パラメーターであると考えることができます。この意味で型パラメーターをとる関数は、伝統的に、何らかの理由で大文字のラムダを使用して記述され/\ます。

それで、id関数を考えてください:

id :: forall a. a -> a
id x = x

型シグネチャから「型パラメーター」を移動し、インライン型注釈を追加して、ラムダとして書き換えることができます。

id = /\a -> (\x -> x) :: a -> a

これは同じことですconst

const = /\a b -> (\x y -> x) :: a -> b -> a

したがって、bar関数は次のようなものになる可能性があります。

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

bar引数として渡される関数の型は、barの型パラメーターに依存することに注意してください。代わりに次のようなものがあったかどうかを検討してください。

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

ここでbar2は、型の何かに関数を適用しているためCharbar2以外の型パラメーターを指定Charすると、型エラーが発生します。

一方、次のようにfooなります。

foo = (\f -> (f Char 't', f Bool True))

とは異なりbarfoo実際には型パラメーターをまったく取りません!それ自体が型パラメーターを受け取る関数を受け取り、その関数を2つの異なる型に適用します。

したがってforall、型シグネチャにa が表示された場合は、型シグネチャのラムダ式と考えてください。通常のラムダと同じように、スコープはforall可能な限り右の括弧で囲まれ、括弧で囲まれます。通常のラムダでバインドされた変数と同様に、aでバインドされた型変数forallは、数量化された式内のスコープ内にのみ存在します。


ポストスクリプト:おそらくあなたは疑問に思うかもしれません-今、型パラメーターを受け取る関数について考えているところで、これらのパラメーターを型シグネチャに入れるよりも興味深いことを実行できないのはなぜですか?答えは私たちができることです!

型変数をラベルと組み合わせて新しい型を返す関数は、型コンストラクターであり、次のように記述できます。

Either = /\a b -> ...

ただし、そのような型の記述方法Either a bは、「Eitherこれらのパラメーターに関数を適用する」ことをすでに示唆しているため、完全に新しい表記法が必要です。

一方、型パラメーターに対して一種の「パターン一致」を行い、型ごとに異なる値を返す関数は、型クラスのメソッドです/\上記の構文を少し拡張すると、次のようになります。

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

個人的には、Haskellの実際の構文を好むと思います...

型パラメーターを「パターンマッチング」し、任意の既存の型を返す関数は、型ファミリーまたは関数の依存関係です。前者の場合、関数定義のように見えます。


1
ここで興味深い見解。これにより、長期的に実を結ぶ可能性がある問題に対する別の見方ができます。ありがとう。
私の正しい意見だけ

@KennyTM:またはλ、その点については、GHCのUnicode構文拡張ではサポートされていません。λは文字なので、架空のビッグラムダ抽象化にも当てはまるという残念な事実です。したがって/\ 、との類推によって\ 。私は使用したばかりかもしれないと思いますが、述語計算を避けようとしていました...
CA McCann

29

これは、あなたがすでによく知っていると思われる簡単な言葉での簡潔で汚い説明です。

forallキーワードは、実際にはHaskellの内の1つの方法で使用されています。あなたがそれを見るとき、それは常に同じことを意味します。

ユニバーサル定量

普遍的定量化タイプは、フォームの種類ですforall a. f a。その型の値は、引数としてを取り、typeを返す関数と考えることができます。ただし、Haskellでは、これらの型引数は型システムによって暗黙的に渡されます。この「関数」は、受け取る型に関係なく同じ値を与える必要があるため、値は多態的です。 af a

たとえば、タイプを考えてみましょうforall a. [a]。その型の値は別の型aを取り、同じ型の要素のリストを返しますa。もちろん、可能な実装は1つだけです。a完全に任意のタイプである可能性があるため、空のリストを提供する必要があります。空のリストは、その要素タイプが多型である唯一のリスト値です(要素がないため)。

またはタイプforall a. a -> a。このような関数の呼び出し元は、型aと型の値の両方を提供しますa。次に、実装は同じ型の値を返す必要がありますa。可能な実装は1つだけです。与えられたのと同じ値を返す必要があります。

存在の定量化

存在量化タイプがフォームを有するであろうexists a. f aHaskellはその表記をサポートしている場合、。そのタイプの値は、次のように考えることができるペア(または「生成物」)タイプからなるaと型の値f a

たとえば、型の値がある場合、ある型exists a. [a]の要素のリストがあります。どのようなタイプでもかまいませんが、それが何であるかわからなくても、そのようなリストに対してできることはたくさんあります。それを逆にしたり、要素の数を数えたり、要素のタイプに依存しない他のリスト操作を実行したりできます。

よし、ちょっと待って。Haskell forallが次のような「存在する」タイプを示すのに使用するのはなぜですか?

data ShowBox = forall s. Show s => SB s

混乱するかもしれませんが、実際にはデータコンストラクターのタイプを 説明していますSB

SB :: forall s. Show s => s -> ShowBox

いったん構築されると、typeの値ShowBoxは2つのもので構成されると考えることができます。それはtype sの値と一緒の型sです。つまり、実存的に定量化された型の値です。Haskellがその表記法をサポートしている場合、ShowBox実際にはと書くことができますexists s. Show s => s

runST と友達

それでは、これらの違いは何ですか?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

まずは撮りましょうbar。型aと型の関数を取り、型a -> aの値を生成します(Char, Bool)Intとして選択して、たとえばaタイプの関数を与えることができますInt -> Int。しかしfoo、違います。それはfoo私たちがそれを与える関数にそれが望むどんな型も渡すことができることが必要です。したがって、合理的に与えることができる唯一の機能はidです。

これで、次のタイプの意味に取り組むことができるはずですrunST

runST :: forall a. (forall s. ST s a) -> a

したがって、どのような型を指定してもrunST、型の値を生成できる必要があります。そうするために、それは確かに何らかの形でを生成しなければならないタイプの引数を使用します。さらに、の実装がとして与えることを決定した型に関係なく、型の値を生成できなければなりません。aaforall s. ST s aaarunSTs

じゃあ何?利点は、これがの呼び出し元に制約を課すことでありrunST、そのため、型には型aをまったく含めることができませんsST s [s]たとえば、タイプの値を渡すことはできません。これが実際に意味することは、の実装runSTはtypeの値で自由に変更を実行できるということですs。タイプは、この変更がの実装に対してローカルであることを保証しrunSTます。

の型runSTは、引数の型に数量詞が含まれているため、ランク2の多相型の例ですforall。上記の型fooもランク2です。のような通常の多相型barはランク1ですが、引数の型が独自のforall数量詞を使用して多型である必要がある場合は、ランク2になります。また、関数がランク2の引数を取る場合、その型はランク3などになります。一般に、rankの多相引数を取る型にはrank nがありn + 1ます。


11

誰かが私が専門用語に染まった数学者であるとは思わない、明確でわかりやすい英語でforallキーワードを完全に説明できますか(または、どこかに存在する場合は、見逃した明確な説明を指摘してください)。

forallHaskellとその型システムのコンテキストでの意味とアプリケーションを説明してみましょう。

しかし、あなたが理解する前に、私はRunar Bjarnasonによる " Constraints Liberate、Liberties Constrain " と題された非常にアクセスしやすい素晴らしい講演にあなたを案内したいと思います。トークには、このステートメントをサポートするためのScalaの例だけでなく、実際の使用例からの例がたくさんありますが、そのことについては触れていませんforallforall以下の見方を説明してみます。

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

この声明を要約して信じて次の説明に進むことが非常に重要なので、話を(少なくともその一部を)ご覧になることをお勧めします。

Haskell型システムの表現力を示す非常に一般的な例は、次の型シグネチャです。

foo :: a -> a

この型シグネチャが与えられると、この型を満たすことができる関数は1つだけであるとidentity言われていidます。

Haskellを習得する最初の段階で、私は常に以下の機能を疑問に思いました。

foo 5 = 6

foo True = False

どちらも上記の型シグネチャを満たしているのに、なぜHaskellの人々idは型シグネチャを満たすのはそれだけであると主張するのでしょうか。

これはforall、型シグネチャに暗黙の隠し要素があるためです。実際のタイプは次のとおりです。

id :: forall a. a -> a

それでは、ステートメントに戻りましょう:制約は解放され、自由は制約されます

これを型システムに変換すると、このステートメントは次のようになります。

型レベルでの制約は、用語レベルで自由になります

そして

タイプレベルの自由は用語レベルの制約になります


最初のステートメントを証明してみましょう:

タイプレベルの制約。

したがって、型シグネチャに制約を課します

foo :: (Num a) => a -> a

用語レベルで自由になると、 これらすべてを書く自由または柔軟性が得られます

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

a他のタイプクラスなどで制約することで同じことが観察できます

したがって、この型シグネチャは次のようにfoo :: (Num a) => a -> a変換されます。

a , st a -> a, a  Num

これは実存定量化として知られています。これは、あるタイプの何かを与えられたときに関数が同じタイプの何かを返すインスタンスいくつか存在することaを意味し、aそれらのインスタンスはすべて一連の数値に属します。

したがって、(a数値のセットに属する必要がある)制約を追加し、用語レベルを解放して複数の可能な実装を持たせることができます。


次に、2番目のステートメントと、実際に次の説明を含むステートメントに移動しforallます。

タイプレベルの自由は用語レベルの制約になります

そこで、型レベルで関数を解放します。

foo :: forall a. a -> a

これは次のように変換されます:

a , a -> a

つまり、この型シグネチャの実装はa -> a、すべての状況に対応できるようにする必要があります。

だから今、これは私たちを学期レベルで制約し始めます。もう書けない

foo 5 = 7

この実装はa、として配置した場合、満足できないためですBoolaすることができCharたり[Char]またはカスタムデータ型を。すべての状況で、同様のタイプの何かを返す必要があります。タイプレベルでのこの自由は、ユニバーサル数量化として知られているものであり、これを満たすことができる唯一の関数は

foo a = a

これは一般にidentity関数として知られています


従ってforallありliberty、その実際の目的であるタイプレベルでconstrain特定の実装に用語レベル。


9

このキーワードのさまざまな使用法がある理由は、それが少なくとも2つの異なる型システム拡張で実際に使用されているためです。つまり、上位の型と実存型です。

おそらく両方で「forall」が両方の構文の適切なビットである理由を説明しようとするのではなく、これらの2つを個別に読んで理解することだけがおそらく最善です。


3

存在性存在性はどうですか?

実存-定量で、forall中のS dataの定義は、その平均値が含まれることができるのである任意の適切なタイプそれことはありませんしなければならないのも、すべての適切なタイプ。- やちるの答え

理由の説明 foralldata定義に同型で(exists a. a)(擬似Haskellは)で見つけることができウィキブックスの「ハスケル/実存種類を定量化しました」

以下は、逐語的な要約です。

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

パターンマッチング/分解するとき MkT x、どのようなタイプxですか?

foo (MkT x) = ... -- -- what is the type of x?

x(で述べたようにforall)任意のタイプにすることができます。そのため、そのタイプは次のとおりです。

x :: exists a. a -- (pseudo-Haskell)

したがって、以下は同型です。

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

forallはforallを意味します

これらすべての私の単純な解釈は、「forall本当に「すべての人」を意味する」ということです。作成する重要な違いはforall定義と機能への影響です。適用です

A forall定義を意味します値または関数のが多態性でなければならないことを。

定義されているものがポリモーフィック値である場合、それは値がすべての適切な値に対して有効でなければならないことを意味しますa。これは非常に制限的です。

定義されるものが多態性関数である場合、それは関数がすべての適切なに対して有効でなければならないことを意味しますa。これは、関数が多態性であるからといって、適用されるパラメーターが多態性である必要があることを意味しないためです。関数はすべてのために有効である場合であること、aは、逆に任意の適切なものはaでき適用します機能に。ただし、パラメーターのタイプは、関数定義で一度しか選択できません。

a forallが関数パラメーターの型(つまりa Rank2Type)内にある場合、適用されるパラメーターは真に多態性である必要があることをforall意味します。この場合、パラメータのタイプは関数定義で複数回選択できます(「そして関数の実装によって選択されます」、ノーマンによって指摘されたように))。

したがって、存在data定義ですべて aが許可される理由は、データコンストラクターが多態性関数であるためです。

MkT :: forall a. a -> T

MkTの種類:: a -> *

これはa、関数に適用できるものがあることを意味します。たとえば、ポリモーフィックなとは対照的に:

valueT :: forall a. [a]

valueTの種類:: a

つまり、valueT の定義はポリモーフィックでなければなりません。この場合、すべてのタイプのvalueT空のリストとして定義でき[]ます。

[] :: [t]

違い

以下のための意味があってもforallで一貫しているExistentialQuantificationRankNType、existentialsので差有するdataコンストラクタは、パターンマッチングに使用することができます。ghcユーザーガイドに記載されているように:

パターンマッチングでは、各パターンマッチにより、存在する型変数ごとに新しい別個の型が導入されます。これらのタイプを他のタイプと統合することはできません。また、パターンマッチのスコープから脱出することもできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.