Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

要約

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"

理由は知らないけどね。

wataru86
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away