LoginSignup
1
1

More than 5 years have passed since last update.

レコードとバリアントの双対性について

Last updated at Posted at 2015-03-07

レコードがあればバリアントがが実装できる.

{-# LANGUAGE Rank2Types #-}
-- パターンマッチ自体をレコードとして表現する
data MatchEither a b c = MatchEither { whenLeft :: a -> c, whenRight :: b -> c }

-- レコードとして表現されたバリアント
newtype RecEither a b = RecEither { unRecEither :: forall c. MatchEither a b c -> c }

-- コンストラクタ
left :: a -> RecEither a b
left x = RecEither (\match -> whenLeft match x)

right :: b -> RecEither a b
right x = RecEither (\match -> whenRight match x)

instance (Show a, Show b) => Show (RecEither a b) where
  show m = unRecEither m (MatchEither {
                           whenLeft = \x -> "Left (" ++ show x ++ ")",
                           whenRight = \x -> "Right (" ++ show x ++ ")" })

instance Monad (RecEither a) where
  return = right
  (>>=) m k = unRecEither m (MatchEither { whenLeft = left, whenRight = \x -> k x })

レコードのそれぞれのフィールドを,パターンマッチのそれぞれの節だと考え,そこからひとつ選び取ることでバリアントを表現する.

レコードなくてもλ式だけでいいんじゃない?

レコードがあればバリアントができると言うけれども,実際のところ,レコードはなくてもいい.
RecEither は forall c. MatchEither a b c -> c のように表現されているが,これは, forall c. (a -> c, b -> c) -> c のようにしても同じことであるし,カリー化して forall c. (a -> c) -> (b -> c) -> c としても,同じことである.すると left x = \f g-> f x となって,けっきょく,λ式だけでいいことになってしまう.

{-# LANGUAGE Rank2Types #-}
newtype LambdaEither a b = LambdaEither { unLambdaEither :: forall c. (a -> c) -> (b -> c) -> c }

-- コンストラクタ
left :: a -> LambdaEither a b
left x = LambdaEither (\whenLeft _ -> whenLeft x)

right :: b -> LambdaEither a b
right x = LambdaEither (\_ whenRight -> whenRight x)

instance (Show a, Show b) => Show (LambdaEither a b) where
  show m = unLambdaEither m (\x -> "Left (" ++ show x ++ ")") (\x -> "Right (" ++ show x ++ ")")

instance Monad (LambdaEither a) where
  return = right
  (>>=) m k = unLambdaEither m left (\x -> k x)

バリアントでレコードを実装する?

双対と言うからには,バリアントを使うことでレコードが実装できそうだが,このことについて触れている文献を,いまのところ知らない.
したがってわたしの独自研究になってしまうわけで,不正確かもしれないが,こういう方法もある.

{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE LambdaCase #-}

data MatchPair a b c = Fst (a -> c) | Snd (b -> c)
newtype VarPair a b = VarPair { unVarPair :: forall c. MatchPair a b c -> c }

pair :: a -> b -> VarPair a b
pair x y = VarPair (\case { Fst f -> f x ; Snd f -> f y })

fst' :: VarPair a b -> a
fst' p = unVarPair p (Fst id)

snd' :: VarPair a b -> b
snd' p = unVarPair p (Snd id)

こっちのほうも,やはり,たとえばペアは \x y z -> z x y` と実装することができるし(Church pair),バリアントをわざわざ使う必要はない.

多相レコードで多相バリアントを実装

レコードでバリアントができるなら,多相レコードで多相バリアントができる.

多相レコードで多相バリアントを実装する方法は, SML# のドキュメントに書かれている.

多相レコードは, Row Polymorphism や, Haskell なら OverloadedRecordFields と呼ばれるものである.

その理屈なら, OverloadedRecordFields を使えば,多相バリアントに当たるものがつくれるはずなのだが,まだちゃんと試していない.

SML# のコードであることに注意.

fun left x match = #whenLeft match x
fun right x match = #whenRight match x
val return = right
infix 1 >>=
fun op >>=(m, k) = m { whenLeft = left, whenRight = (fn x => k x) }

これで left と right は多相バリアントになっている.

例としては,たとえば, left/right/middle というみっつのラベルを持つバリアントがあったとする.

fun left x match = #whenLeft match x
fun right x match = #whenRight match x
val return = right
infix 1 >>=
fun op >>=(m, k) = m { whenLeft = left, whenRight = (fn x => k x) }

fun middle x match = #whenMiddle match x
(*
 * Haskell で書くと
 * bind m k = case m of
 *              Left x -> Left x
 *              Middle x -> Middle x
 *              Right x -> k x
 *)
fun bind(m, k) = m { whenLeft = left, whenMiddle = middle, whenRight = (fn x => k x) }

(* left 1 は >>= に対しても bind に対しても使える!! *)
val l1 = left 1
val it = l1 >>= (fn x => right x)
val it = bind(l1, (fn x => middle x))

多相バリアントで多相レコードを実装

多相バリアントで多相レコードを実装するには,次のようにすればいい.

let id x = x
let pair x y = function `Fst f -> f x | `Snd f -> f y
let fst p = p (`Fst id)
let snd p = p (`Snd id)

let triple x y z = function `Fst f -> f x | `Snd f -> f y | `Third f -> f z
let third p = p (`Third id)
(* fst は pair/triple のどちらに対しても使える *)
let i = fst (pair 1 2)
let j = fst (triple 1 2 3)

参考文献

Chapter 7. SML#の拡張機能:レコード多相性 § 7.7. 多相バリアントの表現
多相レコードで多相バリアントを表現する

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1