拡張可能レコード
標準のレコードが持つ問題点を解決したいくつかのデータ構造・ライブラリ
標準のレコードが持つ問題点
出典: http://nikita-volkov.github.io/record/
- 名前問題が解決できていない。つまり同じモジュールの中で同じフィールド名をもつ2つのレコードを定義できない。例えば以下はコンパイルできない。
data A = A { field :: String }
data B = B { field :: String }
- 部分的である。以下のコードはコンパイル時に何も警告しないのにランタイム時にエラーになる。
data A = A1 { field1 :: String } |
A2 { field2 :: String }
main = print $ field1 $ A2 "abc"
- 型コンストラクタ間で違う型を持つ同じ名前のフィールドを定義することが出来ない。
data A = A1 { field :: String } |
A2 { field :: Int }
- ネストしたレコードの値を更新することは指数関数的に面倒くさくなる
addManStk team = team {
manager = (manager team) {
diet = (diet (manager team)) {
steaks = steaks (diet (manager team)) + 1
}
}
}
OverloadedRecordFields
GHCの言語拡張でレコードの問題を解決しようという試み
- ghc-proposals - Overloaded Record Fields
- GHC Wiki - OverloadedRecordFields
- OverloadedRecordFields revived
- 重複したフィールドラベル
すべての機能が入るまでの道のりは3ステップに分けられる
Step1. DuplicateRecordFields
同じモジュール内で同じフィールド名をもつレコードを定義できるようにする言語拡張
data Person = MkPerson { personId :: Int, name :: String }
data Address = MkAddress { personId :: Int, address :: String }
GHC 8.0 で既に実装されている
Step2. OverloadedLabels
fromLabel @"field" @alpha proxy#
の糖衣構文として #field
のような書き方ができるようになる。 fromLabel
は以下の型クラス IsLabel
のメソッド。
class IsLabel (x :: Symbol) a where
fromLabel :: Proxy# x -> a
具体的に IsLabel
のインスタンスが定義されるわけではないので OverloadedLabels
だけでは使いみちは少ないが将来的にレコードが宣言されると適切なインスタンスが自動的に生成されることを予定している。
GHC 8.0 で既に実装されている
Step3. Magic type classes
~GHCがコンパイル時にレコードの宣言を見つけると以下のような型クラスのインスタンスを作るようになる。~
-- | HasField x r a はrが型aのフィールドxを持つレコードであることを意味している
class HasField (x :: Symbol) r a | x r -> a where
-- | レコードからフィールドを取り出す
getField :: Proxy# x -> r -> a
-- | UpdateField x s t bはsが型tのレコードに型bの値をセットできる
-- フィールドxを持つレコードの型であることを意味している
class UpdateField (x :: Symbol) s t b | x t -> b, x s b -> t where
-- | フィールドをレコードにセットする
setField :: Proxy# x -> s -> b -> t
例えば
data T = MkT { x :: Int }
のようなレコードが定義されると
instance HasField "x" T Int where
getField _ = x
instance UpdateField "x" T T Int where
setField _ (MkT _) x = MkT x
のようなインスタンスが宣言される。
IsLabel
とは以下のようなインスタンスによって結びついている。
instance (HasField x r a) => IsLabel x (r -> a) where
fromLabel = getField (proxy# :: Proxy# x)
ライブラリ
レコードのように扱える独自のデータ構造を使って標準のレコードが持つ問題を解決しようという試み
Vinyl
Extensible records for Haskell with lenses.
- vinyl
- Programming in Vinyl (BayHac 2014)
- Galois Tech Talk / Vinyl: Records in Haskell and Type Theory
- Constant-time
vinyl
Field Getters - Deriving Vinyl Representation from Plain Haskell Records
- Scrap your Nils
record
レコードを表す独自の文法を定義しコンパイル時のプリプロセッサ record-preprocessor
を走らせることを前提としている。
Example
-- Uses the Strict Record syntax.
type Person =
{!
name :: String,
birthday :: {! year :: Int, month :: Int, day :: Int },
country :: Country
}
-- Uses the Lazy Record syntax.
type Country =
{~
name :: String,
language :: String
}
Bookkeeper
GHCが OverloadedLabels
拡張までを提供していることを前提に作られたレコードライブラリ。Template Haskellを使っていないのも特徴。
Example
type Person = Book '[ "name" :=> String, "age" :=> Int ]
julian :: Person
julian = set #name "Julian K. Arni" $ set #age 28 $ emptyBook
-- もしくは
julian :: Person
julian = emptyBook
& #age =: 28
& #name =: "Julian K. Arni"
-- > get #name julian
-- "Julian K. Arni"
rawr
GHCが OverloadedLabels
拡張までを提供していることを前提に作られたレコードライブラリ。TemplateHaskellを使っていないのも特徴。
type Foo = R ( "a" := Int, "b" := Bool )
foo :: Foo
foo = R ( #a := 42, #b := True )
-- > #a foo
-- 42
-- > foo ^. #a
-- 42
-- > -- Extensible Records
-- > R ( #foo := True ) :*: R ( #bar := False )
-- R ( bar := False, foo := True )
Currently, records with up to 8 fields are supported.
という制約がある(参考)
extensible
{-# LANGUAGE OverloadedLabels #-}
type Animal = Record
[ "name" :> String
, "collective" :> String
, "cry" :> Maybe String
]
swan :: Animal
swan = #name @= "swan"
<: #collective @= "lamentation"
<: #cry @= Nothing
<: nil
-- > swan ^. #name
-- "swan"
- extensible
- 割とすぐに始められるextensibleチュートリアル(レコード編)
- Extensible Records Explained
- 波打たせるものの正体(エクステンシブル・タングル)
- extensible-0.4.9 がリリースされました。
- extensible攻略Wiki
- 拡張可能レコードでレコード型を拡縮する (Haskell)
Others
- ruin
- named-record
- Control Flow in Haskell (0) - Introduction
- SuperRecord: Anonymous Records for Haskell
- OVERCOMING THE RECORD PROBLEM
- data-diverse: Extensible records and polymorphic variants.
- Fun with Records in Haskell by making “RowLists” - Qiita
- SuperRecord: Anonymous Records for Haskell
- Alexander Thiemann - SuperRecord: Practical Anonymous Records for Haskell
- Announcing Variant and EADT 2.0
- sketches/has-lens-done-right at master · effectfully/sketches
- Hasパターンとは - Qiita
- Yet another extensible records for Haskell - Qiita
- Forget about lenses, let’s all implement array-backed anonymous records for fun
- [1612.08203] Variations on Variants
- Induction without core-size blow-up
a.k.a. Large records: anonymous edition - Well-Typed: The Haskell Consultants