ことり、穂乃果と一緒に学ぶHaskell(入門)その4「型クラスの定義と実装」

前回

ことり「穂乃果ちゃんとの2人お風呂をしてきた」
穂乃果「言ってくれれば海未ちゃんも呼んで、みんなでお風呂入ったりできたのに」
ことり「えっ……♡♡」


ことり「Cの構造体みたいなデータ型、あれは直積型って言うよ!」

data Member = Member
  { name :: String
  , age :: Int
  }

ことり「オブジェクト指向のクラスと比べた代数的データ型の最大の利点は、その網羅性って言われたりするくらいだしね」
ことり「その裏付けをするのがパターンマッチだよ」


ことり「列挙型について」
ことり「わたしたちμ'sは、こうやって書けるよ」

data Muse = Hanayo | Rin | Maki | Umi | Kotori | Honoka | Eli | Nico | Nozomi

ことり「直和型は、列挙型と直積型が合わさったようなもので、列挙子がフィールドを持ちます!」

data Student = OtonokiStudent String Int
             | UranohoshiStudent String Int Bool
  deriving (Show)

型クラス

ことり「こんにちは〜」
穂乃果「日曜おひさま ほのHaskellだよ!」
ことり「今日は日曜日じゃないけどね♪」

ことり「今日は型クラスについてやっていくよ!」

はい

ことり「型クラスはclassキーワードで定義するよ♪」
穂乃果「class? Javaにもclassキーワードがあったけど、Haskellでは型クラスに使うんだー!」
ことり「うん! Javaのclassはわからないけど、Haskellのclassは『classify(分類する)』から来てるって説があるね」

class GobiPersons a where
  -- 人を受け取って、その人の語尾を返す関数
  say :: a -> String

ことり「これは『語尾持ちとしての性質』を表す型クラスだよ!」
穂乃果「うーん?? 凛ちゃんとかにこちゃんとか……希ちゃんもかな?……かな?」
ことり「うん! 希ちゃんの『やん!』とか『やねっ!』が語尾かは微妙だけど……ちょうど希ちゃんとにこちゃんはサブグループやってたよね」

乙女式れんあい塾
乙女式れんあい塾

ことり「はいっ」

data NozoNico = Nozomi | Nico

ことり「そうしたら、NozoNicoGobiPersonsインスタンスで定義するよ」

instance GobiPersons NozoNico where
  say Nozomi =  "やねっ!"
  say Nico =  "ニコッ!"
main :: IO ()
main = do
  putStrLn (say Nozomi)
  putStrLn (say Nico)

-- vvv 出力 vvv
-- やねっ!
-- ニコッ!

ことり「たまに『型クラスはインターフェースに似ている』とか言われるんだ。 ……賛否両論が多数あるけどね」
穂乃果「あー、でも確かに少し似てるかも……? こんな感じかな?」

interface GobiPersons {
	public String say();
}

enum NozoNico implements GobiPersons {
	Nozomi, Nico;

	@Override
	public String say() {
		if (this == Nozomi) {
			return "やねっ!";
		} else if (this == Nico) {
			return "ニコッ!";
		} else {
			throw new RuntimeException();
		}
	}
}

public class Program {
	public static void main(String[] args) {
		NozoNico nozomi = NozoNico.Nozomi;
		System.out.println(nozomi.say());

		NozoNico nico = NozoNico.Nico;
		System.out.println(nico.say());
	}
}

// vvv 出力 vvv
// やねっ!
// ニコッ!

ことり「Congratulation!」
穂乃果「えへへ〜」

型クラスとインターフェースって、何が違うの?

ことり「ここでひとつ、インターフェースと型クラスの拭えない相違点が浮かんできたよ」
穂乃果「ぬぐえないそういてん?」
ことり「うん。 型クラスはね、derivingを除いて、具体データ型とインスタンス定義が明確に別れてるんだ!」
穂乃果「??? どういうこと?」

ことり「例を見てみよう。 穂乃果ちゃん、ちょっとNozoNicoデータ型をもう1回書いてみてもらってもいい?」
穂乃果「うん! やってみるね!」

data NozoNico = Nozomi | Nico

ことり「じゃあ次はGobiPersonsインスタンスももう1回書いてくれるかな」
穂乃果「はーい」

instance GobiPersons NozoNico where
  say Nozomi =  "やねっ!"
  say Nico =  "ニコッ!"

ことり「ほら、具体データ型の定義とインスタンス定義。 2つの工程が明確に分割されてるんだ」
ことり「対してJavaのインターフェース実装は、必ずクラス定義と同時に行わなくちゃいけない。 って感じかな」

穂乃果「あ、確かに。 でもそれって、便利なの?」
ことり「うん、とても便利だよ! 例えば……ある性質を持つ型クラスをここに定義しました」

-- aとaを足し合わせることができる性質
class Magma a where
  magAppend :: a -> a -> a

ことり「Intって足し算ができるよね?」
穂乃果「うん。 足し算できる」
ことり「でもIntは標準で定義されてるから、後から定義を変更すること……Intにimplements Magmaみたいなのを付け足すことができないんだ」
穂乃果「……あっ!」

ことり「そう、ここで『インスタンス定義が独立していること』が効いてくるよ」

instance Magma Int where  -- 任意の時点でIntのインスタンス定義ができる
  magAppend x y = x + y

main :: IO ()
main = do
  print (magAppend 10 20 :: Int)

-- vvv 出力 vvv
-- 30

穂乃果「ほんとうだ! Haskellすごーい! Haskellすごーい!!」
ことり「でしょー! えへへ」照れり

マグマってだれですか?

穂乃果「そういえば、ことりちゃんがさっき書いてたMagmaって何?」
ことり「ふふっ。 数学者さんでも専門外だと知りすらしない、ちょっと不遇な子かな♪」

参考にしたページ

💎 こんにちは 💎

 この続きはTwitterへの共有ツイート、はてなブックマークへのブックマーク登録、Facebookへのいいね及びシェア、Pocketへの登録
そして僕のモチベーションによって書かれます。
シェアして欲しいな〜〜(シェアはこのページの上部ボタンから!)

 疑問点があれば、Twitterでリプライくれれば(そのリプライを見逃してなければ)返すよ!
@public_ai000ya - Twitter

筆者プロフィール

my-latest-logo

aiya000(あいや)

せつラボ 〜圏論の基本〜」 「せつラボ2~雲と天使と関手圏~」 「矢澤にこ先輩といっしょに代数!」を書いています!

強い静的型付けとテストを用いて、バグを防ぐのが好き。Haskell・TypeScript。