LoginSignup
2
0

入れ子になっているExtensibleレコードの内側の値を操作する

Last updated at Posted at 2021-02-16

要約

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"

理由は知らないけどね。

2
0
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
2
0