Haskell Language
タイプアプリケーション
サーチ…
前書き
TypeApplications
は、コンパイラが与えられた式の型を推測する際に苦労しているときに、 注釈を入力する代わりの方法です。
この一連の例では、 TypeApplications
拡張の目的とその使用方法について説明します。
ソースファイルの先頭に{-# LANGUAGE TypeApplications #-}
を置いて拡張機能を有効にすることを忘れないでください。
型の注釈を避ける
あいまいさを避けるために型注釈を使用します。タイプアプリケーションは、同じ目的のために使用することができます。例えば
x :: Num a => a
x = 5
main :: IO ()
main = print x
このコードにはあいまいなエラーがあります。 a
にはNum
インスタンスがあり、それを印刷するにはShow
インスタンスが必要であることがわかっています。たとえば、 a
がInt
場合a
これが機能します。そのため、エラーを修正するために、型の注釈を追加できます
main = print (x :: Int)
タイプアプリケーションを使用する別のソリューションは、次のようになります
main = print @Int x
これが何を意味するのかを理解するためには、 print
タイプシグネチャを見る必要がありprint
。
print :: Show a => a -> IO ()
この関数はa
型の1つのパラメータを取りますが、実際には2つのパラメータが必要です。最初のものは型パラメーターで、2番目のパラメーターは型が最初のパラメーターである値です。
値パラメータと型パラメータの主な違いは、後者の関数が暗黙的に関数に渡されることです。誰がそれらを提供するのですか?型推論アルゴリズム!どのようなTypeApplications
行うのかは、これらの型パラメータを明示的に与えることです。これは、型推論が正しい型を決定できない場合に特に便利です。
上記の例を分解するために
print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()
他の言語でアプリケーションを入力する
Java、C#、C ++などの言語やジェネリックス/テンプレートの概念に精通している場合は、この比較が役に立ちます。
C#でジェネリック関数があるとします
public static T DoNothing<T>(T in) { return in; }
この関数をfloat
呼び出すには、 DoNothing(5.0f)
か、明示的にしたい場合はDoNothing<float>(5.0f)
と言うことができます。山括弧の中のその部分はタイプアプリケーションです。
ハスケルでは、タイプパラメータがコールサイトだけでなく定義サイトでも暗黙的であるという点を除いて同じです。
doNothing :: a -> a
doNothing x = x
これは、いずれかを使用して明示的に行うことができScopedTypeVariables
、 Rank2Types
またはRankNTypes
このような拡張を。
doNothing :: forall a. a -> a
doNothing x = x
コールサイトでは、 doNothing 5.0
またはdoNothing @Float 5.0
どちらかをdoNothing 5.0
ことができます
パラメータの順序
型引数が暗黙的であるという問題は、複数の型を持つと明らかになります。彼らはどんな順序で来ますか?
const :: a -> b -> a
const @Int
書くa
はInt
と等しいか、それともb
ですか? const :: forall a b. a -> b -> a
ようなforall
を使って型パラメータを明示的にforall
する場合はconst :: forall a b. a -> b -> a
とすると、順序は次のa
になりますb
、 b
。
もしそうでなければ、変数の順序は左から右です。言及されるべき第1の変数は第1の型パラメータであり、第2の型パラメータは第2の型パラメータであり、以下同様である。
2番目の型の変数を指定したいが、最初の変数は指定しない場合はどうなるだろうか?このような最初の変数にワイルドカードを使用できます
const @_ @Int
この式の型は次のとおりです。
const @_ @Int :: a -> Int -> a
あいまいな型との相互作用
サイズがバイトのクラスのクラスを導入しようとしているとします。
class SizeOf a where
sizeOf :: a -> Int
問題は、サイズがそのタイプのすべての値に対して一定でなければならないということです。実際には、 sizeOf
関数はsizeOf
に依存するのでa
なく、その型だけに依存したいと考えてsizeOf
ます。
タイプアプリケーションがなければ、私たちが持っていた最善の解決策は、このように定義されたProxy
タイプでした
data Proxy a = Proxy
このタイプの目的はタイプ情報を運ぶことですが、価値の情報は持ちません。その後、私たちのクラスはこのように見えるかもしれません
class SizeOf a where
sizeOf :: Proxy a -> Int
さて、あなたは最初の議論を完全に落とさないのはなぜだろうか?私たちの関数の型はsizeOf :: Int
か、より正確には、 sizeOf :: SizeOf a => Int
というクラスのメソッドであるか、より明示的なsizeOf :: forall a. SizeOf a => Int
。
問題は型推論です。私がどこかでsizeOf
を書くと、推論アルゴリズムはInt
を期待していることしか知らない。それは私が代わりにしたいどのようなタイプは考えていません。 a
このため、 {-# LANGUAGE AllowAmbiguousTypes #-}
拡張を有効にしない限り 、コンパイラは定義を拒否します。その場合、定義がコンパイルされ、曖昧さのエラーがなければどこでも使用できません。
幸いなことに、タイプアプリケーションの導入はその日を節約します!今、私たちは書くことができるsizeOf @Int
明示的にと言って、あるa
Int
。タイプアプリケーションは、関数の実際のパラメータに現れていなくても、型パラメータを提供することができます !