OverloadedLabels と Haskell Relational Record
Haskell (その2) Advent Calendar 2017 の 18日目のエントリーです。
OverloadedLabels のレコードでの利用
GHC 8.0.1 以降では OverloadedLabels 拡張がサポートされたことにより、 適切な定義を追加しておくことで、レコードのフィールド名の衝突を気にせずに利用できるようになりました。
以下 user's guide の example のコードです。
{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, OverloadedLabels, ScopedTypeVariables #-} import GHC.OverloadedLabels (IsLabel(..)) import GHC.TypeLits (Symbol) data Label (l :: Symbol) = Get class Has a l b | a l -> b where from :: a -> Label l -> b data Point = Point Int Int deriving Show instance Has Point "x" Int where from (Point x _) _ = x instance Has Point "y" Int where from (Point _ y) _ = y instance Has a l b => IsLabel l (a -> b) where fromLabel _ x = from x (Get :: Label l) example = #x (Point 1 2)
IsLabel
クラスのインスタンスを型レベルの文字列 "x"
に対して実装することで、
ここでは #x
というフィールドを取り出す関数名を衝突を気にせずに利用することができます。
OverloadedLabels の Haskell Relational Record での利用
IsLabel
のインスタンスは型レベル文字列 x
と型 a
に対して定義します。
class IsLabel (x :: Symbol) a where fromLabel :: Proxy# x -> a
この定義を良く見ると気がつきますが、 型レベル文字列と任意の型に対するインスタンスを定義することができます。
レコードでの利用の場合は、型レベル文字列とレコードフィールドを取り出す関数に対して定義を行なっているということです。
Haskell Relational Record では型レベル文字列と Pi a b
(レコードからの射影取り出しに利用するラベル) の型に対して定義することで、
射影取り出しのラベルを衝突を気にせずに利用することができます。
Haskell Relational Record でのコード例は例えば以下のようになります。
windowRankByGroup :: Relation () ((Int64, Maybe Int32), (Maybe String, Maybe String)) windowRankByGroup = relation $ do u <- query user m <- query membership on $ #id u .=. #userId m g <- query group on $ #id g .=. #groupId m let gwindow = do partitionBy $ (! #id) g -- g ! Group.id' asc $ #name u return (rank `over` gwindow >< sum' (#id u) `over` gwindow >< (#name u >< #name g))
ラベル #id
や #name
が、衝突を気にせずに利用できていることがわかります。