要約
Haskell の extensible
パッケージが提供する拡張可能レコードのフィールド名は Lens として使えるが、入れ子になっているレコードを操作するときは Data.Extensible.Label.xlb
を入れる必要がある。
この記事の流れ
Lens で入れ子のレコードを操作する方法を確認し、extensible
の拡張可能レコードの場合にどうなるかを見ていく。
Lens の場合
例として、次のように入れ子のレコードを作成する。
#!/usr/bin/env stack
{- stack repl
--resolver lts-17.3
--package lens
-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Hoge = Hoge { _fuga :: Fuga } deriving Show
data Fuga = Fuga { _piyo :: String } deriving Show
makeLenses ''Hoge
makeLenses ''Fuga
hoge :: Hoge
hoge = Hoge {_fuga = fuga'}
fuga' :: Fuga
fuga' = Fuga {_piyo = "aaa"}
^.
や .~
でフィールドの値を操作できる。
ghci
> fuga'
Fuga {_piyo = "aaa"}
> fuga' ^. piyo
"aaa"
> fuga' & piyo .~ "bbb"
Fuga {_piyo = "bbb"}
さらに、関数合成( .
)で入れ子になっているレコードの内側の値も操作できる。
ghci
> hoge
Hoge {_fuga = Fuga {_piyo = "aaa"}}
> hoge ^. fuga . piyo
"aaa"
> hoge & fuga . piyo .~ "bbb"
Hoge {_fuga = Fuga {_piyo = "bbb"}}
Extensible の場合
例として、拡張可能レコードを次のように定義する。
#!/usr/bin/env stack
{- stack repl
--resolver lts-17.3
--package lens
--package extensible
-}
{-# LANGUAGE DataKinds, TypeOperators, OverloadedLabels #-}
import Control.Lens
import Data.Extensible
type Hoge = Record '[ "fuga" >: Fuga ]
type Fuga = Record '[ "piyo" >: String ]
hoge :: Hoge
hoge = #fuga @= fuga <: nil
fuga :: Fuga
fuga = #piyo @= "aaa" <: nil
lens と同じ演算子 ^.
.~
で操作できる。
ghci
> fuga ^. #piyo
"aaa"
> fuga & #piyo .~ "bbb"
piyo @= "bbb" <: nil
しかし、入れ子になっている拡張可能レコードの値をLensと同じように関数合成で操作するとエラーになる。
ghci
> hoge ^. #fuga . #piyo
<interactive>:2:1: error:
• Non type-variable argument
in the constraint: GHC.OverloadedLabels.IsLabel
"fuga" (b -> Hoge -> Const a Hoge)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall b a.
(GHC.OverloadedLabels.IsLabel "fuga" (b -> Hoge -> Const a Hoge),
GHC.OverloadedLabels.IsLabel "piyo" ((a -> Const a a) -> b)) =>
a
> hoge & #fuga . #piyo .~ "bbb"
<interactive>:4:1: error:
• Non type-variable argument
in the constraint: GHC.OverloadedLabels.IsLabel
"fuga" (b1 -> Hoge -> Identity b2)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall b1 b2 a.
(GHC.OverloadedLabels.IsLabel "fuga" (b1 -> Hoge -> Identity b2),
GHC.OverloadedLabels.IsLabel
"piyo" ((a -> Identity [Char]) -> b1)) =>
b2
このような場合、Data.Extensible.Label
モジュールの xlb
をラベルの前に入れると解決する。
ghci
> hoge ^. xlb #fuga . xlb #piyo
"aaa"
> hoge & xlb #fuga . xlb #piyo .~ "bbb"
fuga @= (piyo @= "bbb" <: nil) <: nil
どうやら xlb
を挿入するのは片方のラベルだけでもいいみたいだ。
ghci
> hoge ^. xlb #fuga . #piyo
"aaa"
> hoge ^. #fuga . xlb #piyo
"aaa"
理由は知らないけどね。