web-dev-qa-db-ja.com

パラメトリック多型とより種類の高い型の違いは何ですか?

私はそれらが同じではないと確信しています。ただし、「Rustはサポートしていない」上位型(HKT)ではなく、代わりにパラメトリックポリモーフィズムを提供するという一般的な概念に困惑しています。私はそれを回避してこれらの違いを理解しようとしましたが、ますます絡み合いました。

私の理解では、Rustには、少なくとも基本的にはより種類の高いタイプがあります。 「*」表記を使用すると、HKTには次のような種類があります。 * -> *。たとえば、Maybeは種類* -> *であり、Haskellでこのように実装できます。

data Maybe a = Just a | Nothing

ここに、

  • Maybeは型コンストラクターであり、具体的な型に適用して、種類「*」の具体的な型にする必要があります。
  • Just aおよびNothingはデータコンストラクターです。

Haskellに関する教科書では、これは、より種類の高いタイプの例としてよく使用されます。ただし、Rustでは、列挙として単純に実装できます。これは結局sum typeです:

enum Maybe<T> {
    Just(T),
    Nothing,
}

違いはどこですか?私の理解では、これはより親切なタイプの完全に素晴らしい例です。

  1. HaskellでこれがHKTの教科書の例として使用されている場合、なぜRustにはHKTがありませんか?Maybe enumはHKTとして適格ではありませんか?
  2. むしろRustはHKTを完全にサポートしていない)
  3. HKTとパラメトリック多型の基本的な違いは何ですか?

関数を見るとこの混乱が続きます。Maybeをとるパラメトリック関数を書くことができ、HKTを関数の引数として理解できます。

fn do_something<T>(input: Maybe<T>) {
    // implementation
}

繰り返しになりますが、Haskellでは次のようになります

do_something :: Maybe a -> ()
do_something :: Maybe a -> ()
do_something _ = ()

4番目の質問につながります。

  1. 高級な型のサポートは正確にどこで終了しますか? Rustの型システムがHKTを表現できないようにする最小限の例は何ですか?

関連する質問:

トピックに関連する多くの質問(ブログ投稿へのリンクなど)を試しましたが、主な質問(1と2)に対する答えが見つかりませんでした。

  1. Haskellでは、「より高い種類」は*本当に*型ですか?それとも単に*具体的な*型のコレクションを示すだけですか?
  2. 型パラメーターのない総称型に対する総称構造体
  3. Scalaの高次型
  4. 「より高い種類の多型」の解決に役立つ問題のタイプは何ですか?
  5. Haskellの抽象データ型とパラメトリック多型

更新

非常に詳細で多くの助けとなった多くの良い答えをありがとう。アンドレアス・ロスバーグの答えを受け入れることに決めました。彼の説明が正しい道を行くのに私を最も助けてくれたからです。特に用語に関する部分。

* -> * ... -> *の種類はすべてhigher-kindedであると考えるサイクルに本当に縛られていました。 * -> * -> *(* -> *) -> *の違いを強調した説明は私にとって重要でした。

34
StarSheriff

いくつかの用語:

  • 種類 *は、groundと呼ばれることもあります。 0次と考えることができます。
  • あらゆる種類の* -> * -> ... -> *少なくとも1つの矢印は一次です。
  • higher-order kindは、「左側にネストされた矢印」を持つもの、たとえば(* -> *) -> *

orderは、本質的に、矢印の左側のネストの深さです。たとえば、(* -> *) -> *は2次、((* -> *) -> *) -> *は3次などです(FWIW、同じ概念が型自体にも適用されます。2次関数は、型の形式が例えば(A -> B) -> C。)

非グラウンドカインド(順序> 0)のタイプは、タイプconstructorsとも呼ばれます(一部の文献では、グラウンドタイプのタイプのみを「タイプ」と呼びます)。上位の種類(コンストラクター)は、その種類が高次(順序> 1)であるタイプです。

その結果、より種類の高い型は、非基底の種類の引数を取る型です。それには、多くの言語でサポートされていない非グラウンドの種類の型変数が必要です。 Haskellの例:

type Ground = Int
type FirstOrder a = Maybe a  -- a is ground
type SecondOrder c = c Int   -- c is a first-order constructor
type ThirdOrder c = c Maybe  -- c is second-order

後者の2つはより親切です。

同様に、higher-kinded polymorphismは、基底ではない型を抽象化する(パラメータ的に)多態的な値の存在を表します。繰り返しますが、それをサポートする言語はほとんどありません。例:

f : forall c. c Int -> c Int  -- c is a constructor

Rustは、より高い種類の「代わりに」パラメトリック多相性をサポートするという文は意味がありません。両方とも互いに補完するパラメータ化の異なる次元です。多型。

31

Rustができないことの簡単な例は、HaskellのFunctorクラスのようなものです。

class Functor f where
    fmap :: (a -> b) -> f a -> f b

-- a couple examples:
instance Functor Maybe where
    -- fmap :: (a -> b) -> Maybe a -> Maybe b
    fmap _ Nothing  = Nothing
    fmap f (Just x) = Just (f x)

instance Functor [] where
    -- fmap :: (a -> b) -> [a] -> [b]
    fmap _ []     = []
    fmap f (x:xs) = f x : fmap f xs

インスタンスは、完全に適用されたタイプ[]またはMaybe aではなく、タイプコンストラクターMaybeまたは[a]で定義されることに注意してください。

これは単なるパーラーのトリックではありません。パラメトリック多型と強い相互作用があります。タイプaのタイプ変数bおよびfmapはクラス定義によって制約されないため、Functorのインスタンスはそれらに基づいて動作を変更できません。これは、型からのコードについての推論において信じられないほど強力なプロパティであり、Haskellの型システムの強みがどこから来ているのかということです。

他にも1つのプロパティがあります。より種類の高い型変数で抽象的なコードを記述できます。次に例を示します。

focusFirst :: Functor f => (a -> f b) -> (a, c) -> f (b, c)
focusFirst f (a, c) = fmap (\x -> (x, c)) (f a)

focusSecond :: Functor f => (a -> f b) -> (c, a) -> f (c, b)
focusSecond f (c, a) = fmap (\x -> (c, x)) (f a)

私は認める、これらのタイプは抽象的なナンセンスのように見え始めている。しかし、より高度な抽象化を利用するヘルパーが2人いる場合、それらは非常に実用的です。

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    -- fmap :: (a -> b) -> Identity a -> Identity b
    fmap f (Identity x) = Identity (f x)

newtype Const c b = Const { getConst :: c }

instance Functor (Const c) where
    -- fmap :: (a -> b) -> Const c a -> Const c b
    fmap _ (Const c) = Const c

set :: ((a -> Identity b) -> s -> Identity t) -> b -> s -> t
set f b s = runIdentity (f (\_ -> Identity b) s)

get :: ((a -> Const a b) -> s -> Const a t) -> s -> a
get f s = getConst (f (\x -> Const x) s)

(そこでミスをした場合、誰かが修正することができますか?lensの最も基本的な開始点をコンパイラーなしでメモリから再実装しています。)

focusFirstおよびfocusSecond関数は、getまたはsetのいずれかに最初の引数として渡すことができます。これは、型のf型がgetおよびsetのより具体的な型と統合できるためです。上位の型変数fを抽象化できるため、特定の形状の関数を任意のデータ型のセッターとゲッターの両方として使用できます。これは、lensライブラリーに至る2つのコア洞察のうちの1つです。この種の抽象化なしでは存在できませんでした。

(価値があることに関して、他の重要な洞察は、レンズをそのような関数として定義すると、レンズの合成が単純な関数合成になることです。)

いいえ、型変数を受け入れることができるだけではありません。重要な部分は、具体的な(不明な場合)型ではなく、型コンストラクターに対応する型変数を使用できることです。

16
Carl

パラメトリック多相性とは、関数がその定義で型(または種類)の特定の機能を使用できないというプロパティを指します。完全なブラックボックスです。標準的な例は_length :: [a] -> Int_で、リストのstructureでのみ機能し、リストに保存されている特定の値では機能しません。

HKTの標準的な例はFunctorクラスで、fmap :: (a -> b) -> f a -> f bです。 lengthの種類が_*_であるaとは異なり、fの種類は_* -> *_です。 fmapalsoは、定義でfmapaまたはbのプロパティを使用できないため、パラメトリック多相性を示します。

fmapは、定義canが定義されている特定の型コンストラクターfに合わせて調整されるため、アドホックなポリモーフィズムも示します。つまり、_f ~ []_、_f ~ Maybe_などに対してfmapの個別の定義があります。違いは、fが単にfmapの定義の一部ではなく、タイプクラス定義の一部として「宣言」されることです。 。 (実際、ある程度のアドホックなポリモーフィズムをサポートするためにタイプクラスが追加されました。タイプクラスがない場合、onlyパラメトリックポリモーフィズムが存在します。one具体的なタイプまたは- any具象型ですが、間に小さなコレクションはありません。)

9
chepner

私はそれを再開するつもりです:高種類の型は単なる型レベルの高階関数です。

ただし、少し時間がかかります。

monadトランスフォーマーを検討してください。

newtype StateT s m a :: * -> (* -> *) -> * -> *

ここに、

- s is the desired type of the state
- m is a functor, another monad that StateT will wrap
- a is the return type of an expression of type StateT s m

種類の高いものとは何ですか?

m :: (* -> *)

なぜなら、種類*を受け取り、種類*の種類を返すからです。

型の関数、つまりkindの型コンストラクタのようなものです

* -> *

Javaのような言語では、できません

class ClassExample<T, a> {
    T<a> function()
}

Haskellでは、Tには*->*という種類がありますが、Java型(つまりクラス)には、その種類のより高い種類の型パラメーターを含めることはできません。

また、わからない場合、基本的なHaskellでは、式は種類*を持つ型、つまり「具象型」を持つ必要があります。 * -> *などの他のタイプ。

たとえば、Maybe型の式を作成することはできません。 Maybe IntMaybe Stringなどの引数に適用される型である必要があります。つまり、完全に適用された型コンストラクターです。

8
Federico Sawady