具象型のエンティティを渡した場合のように、ポリモーフィック関数の型をHaskellで印刷できますか?

Dec 12 2020

これが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。

回答

7 MikeSpivey Dec 13 2020 at 06:32

他の回答には、asTypeOfHTNWからの回答の関数など、人為的に制限されたタイプで定義された関数の助けが必要です。次の相互作用が示すように、これは必要ありません。

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

これは、の定義に暗黙的に含まれているラムダバインディングのポリモーフィズムの欠如を利用していますasAppliedTofその本体の両方のオカレンスには同じタイプを指定する必要があり、それが結果のタイプです。constここで使用される関数にも、その自然なタイプがありa -> b -> aます。

const x y = x
12 HTNW Dec 12 2020 at 09:09

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のアイデアのバリエーションと見なすことができます。実装よりも制限の厳しい関数を使用して型推論をガイドします。ただし、自分で定義する必要はありませんが、問題の式をラムダの下にコピーします。

8 K.A.Buhr Dec 12 2020 at 09:57

@HTNWの答えはおそらくそれをカバーしていると思いますが、完全を期すために、inContextソリューションがどのように詳細に機能するかを以下に示します。

関数の型シグネチャ:

inContext :: a -> (a -> b) -> a

つまり、入力したいものと、それが使用される「コンテキスト」(引数として受け取るラムダとして表現可能)がある場合は、次のように入力します。

thing :: a1
context :: a2 -> b

式を作成するだけで、a1(の一般的なタイプthing)とa2(コンテキストの制約)の統合を強制できます。

thing `inContext` context

通常、統合された型thing :: aは失われますが、の型シグネチャはinContext、この結果の式全体の型も目的の型と統合されることを意味し、aGHCiはその式の型を喜んで教えてくれます。

したがって、式:

(.) `inContext` \hole -> hole digitToInt

(.)指定されたコンテキスト内にあるタイプが割り当てられることになります。これは、やや誤解を招くように、次のように書くことができます。

(.) `inContext` \(.) -> (.) digitToInt

(.)匿名ラムダの引数名はそのままでよいのでhole。のトップレベルの定義をシャドウするローカルバインディングを作成しているため、これは混乱を招く可能性がありますが、(.)それでも同じものに(洗練された型で)名前を付けており、ラムダのこの乱用により、元の式を(.) digitToInt逐語的に書くことができました。適切なボイラープレートを使用します。

inContextGHCiにそのタイプを要求しているだけの場合は、実際にはどのように定義されているかは関係ありません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
6 FyodorSoikin Dec 12 2020 at 03:35

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での検索方法は次のとおりです(拡張子は次のとおりです)。

5 chi Dec 12 2020 at 16:39

これは、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