私はそれらが同じではないと確信しています。ただし、「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,
}
違いはどこですか?私の理解では、これはより親切なタイプの完全に素晴らしい例です。
Maybe
enumはHKTとして適格ではありませんか?関数を見るとこの混乱が続きます。Maybe
をとるパラメトリック関数を書くことができ、HKTを関数の引数として理解できます。
fn do_something<T>(input: Maybe<T>) {
// implementation
}
繰り返しになりますが、Haskellでは次のようになります
do_something :: Maybe a -> ()
do_something :: Maybe a -> ()
do_something _ = ()
4番目の質問につながります。
トピックに関連する多くの質問(ブログ投稿へのリンクなど)を試しましたが、主な質問(1と2)に対する答えが見つかりませんでした。
非常に詳細で多くの助けとなった多くの良い答えをありがとう。アンドレアス・ロスバーグの答えを受け入れることに決めました。彼の説明が正しい道を行くのに私を最も助けてくれたからです。特に用語に関する部分。
* -> * ... -> *
の種類はすべてhigher-kindedであると考えるサイクルに本当に縛られていました。 * -> * -> *
と(* -> *) -> *
の違いを強調した説明は私にとって重要でした。
いくつかの用語:
*
は、groundと呼ばれることもあります。 0次と考えることができます。* -> * -> ... -> *
少なくとも1つの矢印は一次です。(* -> *) -> *
。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は、より高い種類の「代わりに」パラメトリック多相性をサポートするという文は意味がありません。両方とも互いに補完するパラメータ化の異なる次元です。多型。
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つです。この種の抽象化なしでは存在できませんでした。
(価値があることに関して、他の重要な洞察は、レンズをそのような関数として定義すると、レンズの合成が単純な関数合成になることです。)
いいえ、型変数を受け入れることができるだけではありません。重要な部分は、具体的な(不明な場合)型ではなく、型コンストラクターに対応する型変数を使用できることです。
パラメトリック多相性とは、関数がその定義で型(または種類)の特定の機能を使用できないというプロパティを指します。完全なブラックボックスです。標準的な例は_length :: [a] -> Int
_で、リストのstructureでのみ機能し、リストに保存されている特定の値では機能しません。
HKTの標準的な例はFunctor
クラスで、fmap :: (a -> b) -> f a -> f b
です。 length
の種類が_*
_であるa
とは異なり、f
の種類は_* -> *
_です。 fmap
alsoは、定義でfmap
がa
またはb
のプロパティを使用できないため、パラメトリック多相性を示します。
fmap
は、定義canが定義されている特定の型コンストラクターf
に合わせて調整されるため、アドホックなポリモーフィズムも示します。つまり、_f ~ []
_、_f ~ Maybe
_などに対してfmap
の個別の定義があります。違いは、f
が単にfmap
の定義の一部ではなく、タイプクラス定義の一部として「宣言」されることです。 。 (実際、ある程度のアドホックなポリモーフィズムをサポートするためにタイプクラスが追加されました。タイプクラスがない場合、onlyパラメトリックポリモーフィズムが存在します。one具体的なタイプまたは- any具象型ですが、間に小さなコレクションはありません。)
私はそれを再開するつもりです:高種類の型は単なる型レベルの高階関数です。
ただし、少し時間がかかります。
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 Int
、Maybe String
などの引数に適用される型である必要があります。つまり、完全に適用された型コンストラクターです。