1. ホーム
  2. scala

[解決済み] 上位互換型はどんなときに役立つのか?

2023-02-28 22:20:34

質問

私はしばらくの間、F#で開発をしていて、それが好きです。 しかし、私が知っている1つの流行語は、F#に存在しない、より高いレベルの型です。 私は高次の型についての資料を読み、その定義を理解しているつもりです。 ただ、なぜそれが有用なのかがよくわからないのです。 ScalaやHaskellでは簡単にできることが、F#では回避策が必要になるという例をどなたか教えていただけませんか? また、これらの例では、高階調型がなければ、どのような回避策があるのでしょうか(F#ではその逆もあります)? 私が回避策に慣れていて、その機能がないことに気づかないだけかもしれません。

の代わりに、(私が思うに) 私はそれを得る myList |> List.map f または myList |> Seq.map f |> Seq.toList より高いキンダーの型では、単純に次のように書くことができます。 myList |> map f と書くと List . それは素晴らしいことですが(それが正しいと仮定して)、ちょっと小難しいように思えますが? (そして、それは単に関数のオーバーロードを許可することによって行うことができなかったのですか?) 私は通常、変換して Seq に変換し、その後で好きなものに変換することができます。 また、私がそれを回避することに慣れすぎているだけなのかもしれません。しかし、より高次の型である が本当に がキーストロークまたは型の安全性のどちらかを節約する例はありますか?

どのように解決するのですか?

つまり、型の種類はその単純な型ということになります。例えば Int は種類 * を持ち、これは基底型であり、値によってインスタンス化されることを意味します。高次元の型の緩やかな定義によって(F#がどこで線引きしているのか分からないので、とりあえず入れておきましょう)。 ポリモーフィックコンテナ は高次元型の素晴らしい例です。

data List a = Cons a (List a) | Nil

タイプ・コンストラクタ List は、種類 * -> * を持っています。これは、具象型を生成するために具象型が渡されなければならないことを意味します。 List Int は次のような住民を持つことができます。 [1,2,3] しかし List 自体はできません。

ポリモーフィックコンテナの利点は明白であると仮定しますが、より便利な種類の * -> * の型はコンテナ以外にも存在します。例えば、リレーション

data Rel a = Rel (a -> a -> Bool)

またはパーサ

data Parser a = Parser (String -> [(a, String)])

もまた、親切な * -> * .


しかし、Haskellではさらに高次の種類を持つ型を持つことで、これをさらに推し進めることができます。例えば、私たちは種類 (* -> *) -> * . この簡単な例としては Shape という種類のコンテナを満たそうとする * -> * .

data Shape f = Shape (f ())

Shape [(), (), ()] :: Shape []

を特徴づけるのに有効です。 Traversable を特徴づけるのに便利です。

split :: Traversable t => t a -> (Shape t, [a])


別の例として、枝の種類をパラメータとする木を考えてみましょう。例えば、通常の木は

data Tree a = Branch (Tree a) a (Tree a) | Leaf

しかし、ブランチ型には PairTree a のように、パラメトリックに型からその部分を取り出すことができます。

data TreeG f a = Branch a (f (TreeG f a)) | Leaf

data Pair a = Pair a a
type Tree a = TreeG Pair a

これは TreeG 型コンストラクタは (* -> *) -> * -> * . のような他の面白いバリエーションを作るために使うことができます。 RoseTree

type RoseTree a = TreeG [] a

rose :: RoseTree Int
rose = Branch 3 [Branch 2 [Leaf, Leaf], Leaf, Branch 4 [Branch 4 []]]

のような病的なものも MaybeTree

data Empty a = Empty
type MaybeTree a = TreeG Empty a

nothing :: MaybeTree a
nothing = Leaf

just :: a -> MaybeTree a
just a = Branch a Empty

または TreeTree

type TreeTree a = TreeG Tree a

treetree :: TreeTree Int
treetree = Branch 3 (Branch Leaf (Pair Leaf Leaf))


これが現れるもう一つの場所は、"ファンクタの代数"です。もし、抽象化のレイヤーをいくつか落とすと、これは次のようなフォールドとして考えた方が良いかもしれません。 sum :: [Int] -> Int . 代数のパラメータは ファンクタ キャリア . は ファンクタ は、種類 * -> * を持ち、キャリアの種類は * ということで、全部で

data Alg f a = Alg (f a -> a)

は種類 (* -> *) -> * -> * . Alg は、データ型やその上に構築された再帰的スキームとの関係から、有用です。

-- | The "single-layer of an expression" functor has kind `(* -> *)`
data ExpF x = Lit Int
            | Add x x
            | Sub x x
            | Mult x x

-- | The fixed point of a functor has kind `(* -> *) -> *`
data Fix f = Fix (f (Fix f))

type Exp = Fix ExpF

exp :: Exp
exp = Fix (Add (Fix (Lit 3)) (Fix (Lit 4))) -- 3 + 4

fold :: Functor f => Alg f a -> Fix f -> a
fold (Alg phi) (Fix f) = phi (fmap (fold (Alg phi)) f)


最後に、理論的には可能ですが、私はこれまで であっても より高次の型コンストラクタを見たことがありません。そのような型の関数は時々見かけますが、例えば mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b しかし、型の複雑さについては、型プロローグや依存型付けされた文献を調べないとわからないと思います。