Freer Effectsが、だいたいわかった: 11-5 MultiParamTypeClasses拡張
目次
(0). 導入
-
Freeモナドの概要
- Freeモナドとは
- FreeモナドでReaderモナド、Writerモナドを構成する
- 存在型(ExistentialQuantification拡張)の解説
- 型シノニム族(TypeFamilies拡張)の解説
- データ族(TypeFamilies拡張)の解説
- 一般化代数データ型(GADTs拡張)の解説
- ランクN多相(RankNTypes拡張)の解説
-
FreeモナドとCoyoneda
- Coyonedaを使ってみる
- FreeモナドとCoyonedaを組み合わせる
- いろいろなモナドを構成する
-
Freerモナド(Operationalモナド)でいろいろなモナドを構成する
- FreeモナドとCoyonedaをまとめて、Freerモナドとする
- Readerモナド
- Writerモナド
- 状態モナド
- エラーモナド
-
モナドを混ぜ合わせる(閉じた型で)
- Freerモナドで、状態モナドとエラーモナドを混ぜ合わせる
- 両方のモナドを一度に処理する
- それぞれのモナドを、それぞれに処理する
- Freerモナドで、状態モナドとエラーモナドを混ぜ合わせる
- 存在型による拡張可能なデータ構造(Open Union)
- 追加の言語拡張
- ScopedTypeVariables拡張
- TypeOperators拡張
- KindSignatures拡張
- DataKinds拡張
- MultiParamTypeClasses拡張
- FlexibleInstances拡張
- OVERLAPSプラグマ
- FlexibleContexts拡張
- LambdaCase拡張
- Open Unionを型によって安全にする
- モナドを混ぜ合わせる(開いた型で)
- FreeモナドとOpen Unionを組み合わせる
- 状態モナドにエラーモナドを追加する
- Freer Effectsで、IOモナドなどの、既存のモナドを使用する
- 関数を保管しておくデータ構造による効率化
- いろいろなEffect
- 関数handleRelayなどを作成する
- NonDetについて、など
はじめに
プレーンなHaskellでは、型クラスのとれる引数は、ひとつである。言語拡張を使うことで、型クラスが複数の引数をとれるようにできる。
Ruby的かけ算
何がしたいか
Rubyでは、文字列と数値とをかけあわせることができる。もちろん、数値と数値とをかけあわせることもできる。これを実現するために、かけ算の第1引数と第2引数とを、別の型にできるような、多相的なかけ算を定義してみよう。
コードと実行例
つぎのような、ファイルmul.hsを作成する。
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
import Prelude hiding ((*))
import qualified Prelude
import qualified Data.ByteString as BS
class Mulable a b where
(*) :: a -> b -> a
かけ算の結果の値の型は、第1引数の型とおなじ型とする。「文字列 * 整数」についてインスタンスを定義する。
instance Mulable BS.ByteString Int where
bs * n = BS.concat $ replicate n bs
instance Mulable BS.ByteString Integer where
bs * n = BS.concat $ replicate (fromInteger n) bs
計算例を定義する。
threeHello :: BS.ByteString
threeHello = "Hello" * (3 :: Int)
sevenHello :: BS.ByteString
sevenHello = "Hello" * (7 :: Int)
対話環境でみてみよう。
> :load mul.hs
> threeHello
"HelloHelloHello"
> sevenHello
"HelloHelloHelloHelloHelloHelloHello"
「Int型の値 * 整数」についてインスタンス定義する。
instance Mulable Int Int where
n * m = n Prelude.* m
instance Mulable Int Integer where
n * m = n Prelude.* fromInteger m
計算例を定義する。
threeFive :: Int
threeFive = 5 * (3 :: Int)
sevenFive :: Int
sevenFive = 5 * (7 :: Integer)
対話環境でみてみる。
> :reload
> threeFive
15
> sevenFive
35
もりあがってきたので、「文字列 * 浮動小数点数」や「Int型の値 * 浮動小数点数」も定義する。
instance Mulable BS.ByteString Double where
bs * x = BS.take l . BS.concat $ replicate (ceiling x) bs
where l = round $ fromIntegral (BS.length bs) prelude.* x
instance Mulable Int Double where
n * x = round $ fromIntegral n Prelude.* x
threePointFourHello :: BS.ByteString
threePointFourHello = "Hello" * (3.4 :: Double)
threePointFourFive :: Int
threePointFourFive = 5 * (3.4 :: Double)
試してみよう。
> :reload
> threePointFourHello
"HelloHelloHelloHe"
> threePointFourFive
17
設計のまずさ
わかりやすい例として挙げたけれど、上のような設計をするのは、あまりかしこいとは言えない。かけ算の第1引数となる型の数nと、第2引数となる型の数mに対して、n * m個のインスタンス宣言が必要になる。かしこい設計は、おそらく、「いちど共通の型に変換して云々」といったところか。プログラミングの名著中の名著であるSICP(Structure and Interpretation of Computer Programs)あたりで触れられていたように思う。
まとめ
MultiParamTypeClasses拡張を使うと、型クラスが複数の引数をとることができるようになる。わかりやすい例として「Rubyっぽいかけ算」の例を挙げた。この例は「わかりやすい」けれど、「かしこくない設計」ではある。