generated at
アドホック多相と型クラスらへん
タイトルや残す場所は後で考えるmrsekut








この記事とそのコメントを読んで、楽しく見ていくためにはmrsekutのあらゆる面での知識が足りていない気がしてきた
個々の多相性の理論的背景
parametric
ad hoc
sub typing
実際の機能の能力
type class
trait
interface
protocol
concepts
その他、関連する項目
HKT
動的dispatch、静的dispatch
なのでいったん保留


型クラスでもinterfaceでもできること
Hoge を実装する型が満たすべき条件を記述
実装時に、それらの条件を満たすことを強制する
genericsの型引数の制約として用いる
interfaceでしかできないこと
Animalという(Interface|type class)な値を作ることはできない
Interface、classは型だが、
type classは型ではない
型の上位概念
型クラス出来しかできないこと
self的なもの
hs
class Eq a where (==) :: a -> a -> Bool
swift
protocol Equatable { static func ==(lhs: Self, rhs: Self) -> Bool }
rs
trait Equatable { fn equals(&self, other: &Self) -> bool; }
protocolは、Selfを使わない場合はsubtyping多相として、値に対する型として使えるが、Selfを使うと型クラス的になり、subtypyng多相はできなくなる
traitも多分そう
traitでSelfを使うと次元が上がった風に感じたのはそのためかmrsekut
たしかに、これ言われてみれば普通のinterfaceでは無理なのか
ts
interface Equatable { equals(other: Equatable): boolean } class Cat implements Equatable { equals(other: ???): boolean { return true; } }
equalsの引数をCatに強制できない
実装者が明示的に書くことはできるが、interfaceで強制できない
これ、型理論で言うと何になるんだろう
mapの抽象化
hs
class Functor f where fmap :: (a -> b) -> f a -> f b
これ、rustの場合は、generic traitが書けるからtraitでもできちゃうのか
たぶん




型クラス自体は、ad hoc多相
型クラス制約は、ad hoc多相 + parametric多相
Javaでは書けない



おもろ




型クラスはインターフェースと似ているが
目的が異なるものなので、そもそも比べるものではない
たしかに型クラスはInterface
型はclassと考えれば、そうだなってなるがそうでもないのか
目的が異なる
インターフェースは、それを継承したクラスに実装を強制する仕組み
型クラスは紐付いている型クラスのmethodを自動的に挿入するもの
定義と実装の場所

後から型クラスのインスタンスにできる
自作型でも既存の型でも、途中で、あ、これあの型クラスのインスタンスにしたいな、って思ったタイミングで、その型クラスのインスタンスにできる
新たな性質がほしければ新しく型クラスを定義してそのインスタンスにすればいい
Javaなどではclassを定義するタイミングでinterfaceをimplementsしないといけない
あとから付け足したければそのclassをextendsした新しいclassを作るとか?

型クラスを見ても、interfaceと似てるとはあまり思わなかったけど、traitをみるとかなりそう見える

Rustのtraitの復習
ad hoc多相と、interfaceの違い
型クラスの継承と、OOPの継承の類似点、差異



この記事でoverloadからの型クラスへの流れで解説されている
要はoverloadは型クラスの下位互換のようなものだと
疑似.hs
sum :: [Int] -> Int sum [] = 0 sum (x:xs) = x + sum xs sum :: [String] -> String sum [] = "" sum (x:xs) = x ++ sum xs sum :: [Hoge] -> Hoge sum ...
同名の関数を型ごとに定義するoverloadを使うことで同じ様な実装ができる
嬉しい点
既存の型を触らずに済む
class的な実装の場合は、上の例ならば Hoge classの中にmethodを追加することになる
問題点
実装が重複する。ほとんど同じなのに型の数だけ実装を書く必要がある
例えば空配列が来た場合はどちらもその型のdefault値を返している
新しい型 Piyo を作った際にこれに対してsumが欲しければPiyoの実装を追加で書く必要がある
型をさらに抽象化した概念である型クラスを導入する
hs
class Addable a where unit :: a add :: a -> a -> a instance Addable Int where unit = 0 add = (+) sum :: (Addable a) => [a] -> a sum [] = unit sum (x:xs) = add x (sum xs)
とある型をこの型クラスのインスタンスにするためには、methodを実装する必要がある
それさえ用意しておけば、 Addable のインスタンス向けの関数(ここではsum)の実装を1回かけば、あとはそれを使い回すことができる
既存のAddbaleのインスタンスの型全てで使えるし、
まだ存在していない未来のAddabelのインスタンスの型全てでも使える
拡張性
Addable向けに新たな関数がほしいとき
Addable向けに一つ実装すれば、他の型に対しても使える
ex. double
Piyo 型でsumを使いたいとき
型クラスがあれば、overloadは不要?
そうじゃないなら何故必要?
ちなみにHaskellにはoverloadはない
overloadのデメリットと、型クラスがそれをどう解決したか
「型クラス」「interface」「アドホック多相」「overload」の関係性
多相的な関数を効率よく作れるのが嬉しいのか?
これが本質?
アドホック多相の上位互換が本質ということ?


アドホック多相の限界
SMLのoverloadの問題点

算術演算子は数値型に対して多相だが、それを用いた関数を定義する事ができない
(+) など
疑似.hs
add a b = a + b add 1 2 // できない add 1.0 2.0 // できない
これを、裏でコンパイラがoverloadした関数を取りうるように扱うとすると、
上の例の場合は以下の2つが自動的に生成されることになる
hs
add :: Int -> Int -> Int add :: Float -> Float -> Float
しかし、こういう関数を複数とる関数を定義すると、その数は指数関数的に増える
hs
adds (x, y, z) = (add x x, add y y, add z z)
上の例だけでも2^3個の adds 関数を生成しないといけない
Mirandaはそもそも数値型が num しかないのでこういう問題は起きない
が、その分表現力が低い
Standard MLは実際に↑をしたとき、8個の関数が生成されている?


== の多相性にはいくつかのアプローチがある
overloadする
うえの add の例と同じで、裏で複数の型に対応した関数を生成する
対象となる型は、primitiveな単一の型
関数型や、抽象型は含まない
これを使った多相関数は定義できない
hs
member [] y = False member (x:xs) y = (x == y) \/ member xs y
これは算術演算子の例と同等の理由で定義できない
任意の型に適用するために生成しまくると増えすぎるから
算術演算子は特別扱いで免れているのでいけているだけ、って感じなのかなmrsekut
== を全面的に多相にする
(==) :: a -> a -> Bool にする
a は任意の型なので、型システム的には関数型も許容されることになる
静的検査を通過する
しかし、実装はそうはなっていないのでランタイムエラーになる
抽象型も許容する
データ表現同士の比較をする
これは、抽象の原理を違反している #??
なにが問題なのかわからんmrsekut
Mirandaがこれ
== を部分的に多相にする
(==) :: a(==) -> a(==) -> Bool にする
a(==) は等値比較を許す型を表す
つまり a(==) 型には、関数型や抽象型は含まれな
なのでその辺は静的検査時に弾くことができる
Standard MLがこれ
''a とかく
eqtype variableと呼ばれる
OOPの話がいまいち理解できていない
型クラスのアイディアの背景にこれがある

そもそもなぜoverloadが必要になるのか
静的型付けだから #??




hs
-- in Library class Addable a where unit :: a add :: a -> a -> a sum' :: (Addable a) => [a] -> a sum' [] = unit sum' (x : xs) = add x (sum' xs) -- in User instance Addable Int where unit = 0 add = (+) main :: IO () main = print $ sum' ([1, 2, 3, 4] :: [Int])
ts
interface Addable<T> { value: T; sum: (a: T[]) => Addable<T>; } class AddableInt implements Addable<number> { value: number; constructor(a: number) { this.value = a; } sum(a: number[]) { return new AddableInt(a.reduce((acc, cur) => acc + cur, 0)); } }


Monad作るときに、 return >>= を定義するのもめちゃくちゃアドホック多相だもんな

Concepts
この論文のことかわからないけど、C++のConceptsの初出(?)の論文で型クラスを引き合いに出し点ものがあるらしい ref 『Masterminds of Programming』 p.206

型クラスはJavaのジェネリックにも影響を与えた
以下の二つは同じことを意味する
Javaの型変数がインターフェースを継承すると宣言すること
java
public static <T extends Comparable<T>> T min (T x, T y) { if (x.compare(y) < 0) return x; else return y; }
Haskellの型変数が型クラスに属すると定義すること
hs
min :: Ord a => a -> a -> a min x y = if x < y then x else y

Rustのトレイと




参考
↑には2つの嬉しい点について言及されている
1つ目は上に書いたようなoverloadの話
2つ目は等価判定の話
こっちがよくわからないmrsekut
MLの問題点の方をまず理解しチア
Haskellの型クラスの定義は雑すぎる
CoqやIsabelleの型クラスの定義はかなり厳密らしい