具象型のエンティティを渡した場合のように、ポリモーフィック関数の型をHaskellで印刷できますか?
これが3つのタイプの多型関数です:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
そしてここで非ポリモーフィック関数:
:t Data.Char.digitToInt
Data.Char.digitToInt :: Char -> Int
前者を後者に適用すると、1つのタイプで多型関数が得られます。
:t (.) Data.Char.digitToInt
(.) Data.Char.digitToInt :: (a -> Char) -> a -> Int
その手段(.)
(私は必ずこれが正しい用語ではないよ; C ++プログラマーとして、私はそれを呼び出すと思います)、「インスタンス化」されたとb === Char
してc === Int
、そのの署名(.)
に適用されますそれはdigitToInt
以下のとおりであります
(Char -> Int) -> (a -> Char) -> a -> Int
私の質問は、この署名を画面に印刷して、与えられた(.)
ものdigitToInt
と、前者を後者に適用したい「情報」を表示する方法はありますか?
興味を持って誰のために、この質問は、以前の重複として閉鎖されたこの1。
回答
他の回答には、asTypeOf
HTNWからの回答の関数など、人為的に制限されたタイプで定義された関数の助けが必要です。次の相互作用が示すように、これは必要ありません。
Prelude> let asAppliedTo f x = const f (f x)
Prelude> :t head `asAppliedTo` "x"
head `asAppliedTo` "x" :: [Char] -> Char
Prelude> :t (.) `asAppliedTo` Data.Char.digitToInt
(.) `asAppliedTo` Data.Char.digitToInt
:: (Char -> Int) -> (a -> Char) -> a -> Int
これは、の定義に暗黙的に含まれているラムダバインディングのポリモーフィズムの欠如を利用していますasAppliedTo
。f
その本体の両方のオカレンスには同じタイプを指定する必要があり、それが結果のタイプです。const
ここで使用される関数にも、その自然なタイプがありa -> b -> a
ます。
const x y = x
Prelude
:の隅に隠されたこのきちんとした小さな機能があります:
Prelude.asTypeOf :: a -> a -> a
asTypeOf x _ = x
「最初の引数を2番目の引数と同じ型にするように強制する」と文書化されています。これを使用して、(.)
の最初の引数のタイプを強制できます。
-- (.) = \x -> (.) x = \x -> (.) $ x `asTypeOf` Data.Char.digitToInt -- eta expansion followed by definition of asTypeOf -- the RHS is just (.), but restricted to arguments with the same type as digitToInt -- "what is the type of (.) when the first argument is (of the same type as) digitToInt?" ghci> :t \x -> (.) $ x `asTypeOf` Data.Char.digitToInt
\x -> (.) $ x `asTypeOf` Data.Char.digitToInt
:: (Char -> Int) -> (a -> Char) -> a -> Int
もちろん、これは必要な数の引数に対して機能します。
ghci> :t \x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
\x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
:: (Char -> Int) -> ([Char] -> Char) -> [Char] -> Int
これは、コメントで@KABuhrのアイデアのバリエーションと見なすことができます。実装よりも制限の厳しい関数を使用して型推論をガイドします。ただし、自分で定義する必要はありませんが、問題の式をラムダの下にコピーします。
@HTNWの答えはおそらくそれをカバーしていると思いますが、完全を期すために、inContext
ソリューションがどのように詳細に機能するかを以下に示します。
関数の型シグネチャ:
inContext :: a -> (a -> b) -> a
つまり、入力したいものと、それが使用される「コンテキスト」(引数として受け取るラムダとして表現可能)がある場合は、次のように入力します。
thing :: a1
context :: a2 -> b
式を作成するだけで、a1
(の一般的なタイプthing
)とa2
(コンテキストの制約)の統合を強制できます。
thing `inContext` context
通常、統合された型thing :: a
は失われますが、の型シグネチャはinContext
、この結果の式全体の型も目的の型と統合されることを意味し、a
GHCiはその式の型を喜んで教えてくれます。
したがって、式:
(.) `inContext` \hole -> hole digitToInt
(.)
指定されたコンテキスト内にあるタイプが割り当てられることになります。これは、やや誤解を招くように、次のように書くことができます。
(.) `inContext` \(.) -> (.) digitToInt
(.)
匿名ラムダの引数名はそのままでよいのでhole
。のトップレベルの定義をシャドウするローカルバインディングを作成しているため、これは混乱を招く可能性がありますが、(.)
それでも同じものに(洗練された型で)名前を付けており、ラムダのこの乱用により、元の式を(.) digitToInt
逐語的に書くことができました。適切なボイラープレートを使用します。
inContext
GHCiにそのタイプを要求しているだけの場合は、実際にはどのように定義されているかは関係ありませんinContext = undefined
。しかし、型の署名を見るだけで、inContext
実用的な定義を与えるのは簡単です。
inContext :: a -> (a -> b) -> a
inContext a _ = a
これは単なる定義であることが判明したconst
のでinContext = const
、同様に機能します。
inContext
一度に複数のものを入力するために使用でき、名前の代わりに式にすることができます。前者に対応するために、タプルを使用できます。後者が機能するためには、ランバでより賢明な引数名を使用する必要があります。
したがって、たとえば:
λ> :t (fromJust, fmap length) `inContext` \(a,b) -> a . b
(fromJust, fmap length) `inContext` \(a,b) -> a . b
:: Foldable t => (Maybe Int -> Int, Maybe (t a) -> Maybe Int)
式fromJust . fmap length
では、タイプは次のことに特化していることを示しています。
fromJust :: Maybe Int -> Int
fmap length :: Foldable t => Maybe (t a) -> Maybe Int
TypeApplications
拡張機能を使用してこれを行うことができます。これにより、タイプパラメータをインスタンス化するために使用するタイプを明示的に指定できます。
λ :set -XTypeApplications
λ :t (.) @Char @Int
(.) @Char @Int :: (Char -> Int) -> (a -> Char) -> a -> Int
引数は正確な順序でなければならないことに注意してください。
のような「通常の」型シグネチャを持つ関数のfoo :: a -> b
場合、順序は、型パラメータが最初にシグネチャに現れる順序によって定義されます。
のExplicitForall
ようfoo :: forall b a. a -> b
に使用する関数の場合、順序はの中にあるものによって定義されforall
ます。
(どのタイプを入力するかを知るだけでなく)適用(.)
に基づいてタイプを明確に把握したい場合digitToChar
は、GHCiではできないと確信していますが、HaskellIDEのサポートを強くお勧めします。
たとえば、VSCodeでの検索方法は次のとおりです(拡張子は次のとおりです)。

これは、HTNWの回答のマイナーなバリエーションです。
多形識別子を含む、潜在的に大きな式があるとします。 poly
.... poly ....
その時点で、ポリモーフィック型がどのようにインスタンス化されたのか疑問に思います。
これは、GHCの2つの機能asTypeOf
(HTNWで言及されている)とタイプされた穴を利用して、次のように実行できます。
.... (poly `asTypeOf` _) ....
_
穴を読み取ると、GHCはその穴の代わりに入力する必要がある用語のタイプを報告するエラーを生成します。を使用したのでasTypeOf
、これpoly
はそのコンテキストで必要な特定のインスタンスのタイプと同じである必要があります。
GHCiの例を次に示します。
> ((.) `asTypeOf` _) Data.Char.digitToInt
<interactive>:11:17: error:
* Found hole: _ :: (Char -> Int) -> (a -> Char) -> a -> Int