extensible/拡張可能積
また、以下の内容が読み込まれているものと仮定する。
code: (hs)
{- stack repl
--resolver lts-12.11
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE OverloadedLabels #-} import Data.Extensible
import Data.Proxy
type Person = Record
'[ "name" >: String
, "age" >: Int
]
基本操作
nil, (<:)
拡張可能レコードを作る時に利用する。
code: (hs)
person :: Person
person = #name @= "guchi" <: nil
実行結果
code: (shell)
*Main> person
name @= "guchi" <: age @= 10 <: nil
hlength
拡張可能レコードのフィールド数を求める。
code: (hs)
exHlength :: Int
exHlength = hlength person
実行結果
code: (shell)
*Main> exHlength
2
happend
2つの拡張可能レコードを結合する。
code: (hs)
exHappend = happend person person
実行結果
code: (shell)
*Main> exHappend
name @= "guchi" <: age @= 10 <: name @= "guchi" <: age @= 10 <: nil
hsequence
<@=> と組み合わせると強い。
code: (hs)
person' :: Either String Person
person' =
hsequence $ #name <@=> Right "guchi" <: nil
person'' :: Either String Person
person'' =
hsequence $ #name <@=> Left "error" <: nil
実行結果
code: (shell)
*Main> person'
Right (name @= "guchi" <: age @= 10 <: nil)
*Main> person''
Left "error"
制約付き畳み込み
hfoldMapFor
拡張可能レコードのフィールドに対して、総称的な関数を適用し、mappendで結合する。
code: (hs)
-- exHfoldMapFor :: Person -> String のように、具体的な型を指定しても良い exHfoldMapFor :: Forall (ValueIs Show) xs => Record xs -> String exHfoldMapFor = hfoldMapFor c ((:[]) . show . getField)
where
c = Proxy @ (ValueIs Show)
実行結果
code: (shell)
*Main> exHfoldMapFor person
また、モノイドを変更すれば結果も異なる。
code: (hs)
-- exHfoldMapFor' :: Person -> String のように、具体的な型を指定しても良い
exHfoldMapFor' :: Forall (ValueIs Show) xs => Record xs -> String
exHfoldMapFor' = hfoldMapFor c (show . getField)
where
c = Proxy @ (ValueIs Show)
実行結果
code: (shell)
*Main> exHfoldMapFor' person
"Identity \"guchi\"Identity 10"
First モノイドの例 (import Data.Monoid が必要)
code: (hs)
-- exHfoldMapFor'' :: Person -> First String のように、具体的な型を指定しても良い
exHfoldMapFor'' :: Forall (ValueIs Show) xs => Record xs -> First String
exHfoldMapFor'' = hfoldMapFor c (First . Just . show . getField)
where
c = Proxy @ (ValueIs Show)
実行結果
code: (shell)
*Main> exHfoldMapFor'' person
First {getFirst = Just "Identity \"guchi\""}
以下は key と val を全て String モノイドで結合する例。関数 g の型と制約の付け方は参考になるだろう。また、keyとvalの両方に制約をつけるためにAndコンビネータを利用している点にも着目して欲しい。
code: (hs)
{- stack repl
--resolver lts-12.11
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PolyKinds #-} import Data.Extensible
import Data.Proxy
import GHC.TypeLits
import Data.Functor.Identity
import Control.Arrow
type Person = Record
'[ "name" >: String
, "age" >: Int
]
f :: Person -> String
f = hfoldMapFor c (uncurry (<>) . (stringAssocKey &&& g))
where
g :: (Show (AssocValue kv)) => Field Identity kv -> String
g = show . runIdentity . getField
c = Proxy @ (And (KeyIs KnownSymbol) (ValueIs Show))
person :: Person
person
<: nil
実行結果
code: (shell)
*Main> f person
"name\"guchi\"age10"
hfoldMapForの内部で副作用を扱うことも可能だ。以下の例のdebug関数は与えられた拡張可能レコードの値と型を表示する。
code: (hs)
{- stack repl
--resolver lts-12.11
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PolyKinds #-} import Data.Extensible
import Data.Functor.Identity
import Data.Typeable
import Control.Arrow
type Person = Record
'[ "name" >: String
, "age" >: Int
]
debug :: Forall (ValueIs (And Show Typeable)) xs => Record xs -> IO ()
debug = hfoldMapFor c (print . (id &&& typeOf) . runIdentity . getField)
where
c = Proxy @ (ValueIs (And Show Typeable))
person :: Person
person
<: nil
実行結果
code: (hs)
*Main> debug person
(10,Int)