レコードのラベルの重複

Gotanda.hs #1 @HERP

岡本和樹 @kakkun61

もくじ

  • つらみ
  • GHC 提案
  • Overloaded Record Fields (Redesign)
  • DuplicateRecordFields
  • OverloadedLabels
  • Magic type classes
  • Record Set Field Proposal

つらみ

よくあるつらみ

data Person =
  Person
    { personName :: String
    , personAge :: Word
    , personCompany :: Maybe Company
    }
data Company = Company { companyName :: String }

name って名前にしたいやん

接頭辞付けたくないやん

GHC 提案

Overloaded Record Fields (Redesign)

#name という特殊な記法で多相な関数が使えるようになる

data Person = Person { name :: String }
data Company = Company { name :: String }

let me = Person "Kazuki"
#name me -- > Kazuki

大きい提案だったので分割された

  1. DuplicateRecordFields (in GHC 8.0)
  2. OverloadedLabels (in GHC 8.0)
  3. Magic type classes (partly in GHC 8.2)

DuplicateRecordFields

重複したレコードのラベルを定義できるようになる

data Person = Person { name :: String }
data Company = Company { name :: String }

関数的な使用はできない

let me = Person "Kazuki"
name me
-- Ambiguous occurrence ‘name’

パターンマッチでは使える

hello Person { name = name } = "Hello, " ++ name ++ "."
hello me

個人的には DuplicateRecordFieldsNamedFieldPuns で大半のケースは楽になる

hello Person { name } = "Hello, " ++ name ++ "."
hello me

OverloadedLabels

#name という記法が使えるようになる

#name me -- > "Kazuki"

そのためには準備が必要

:set -XDataKinds -XFlexibleInstances -XMultiParamTypeClasses

import GHC.OverloadedLabels (IsLabel (fromLabel))

instance IsLabel "name" (Person -> String) where
  fromLabel Person { name } = name

使用側

:set -XFlexibleContexts
#name me :: String

しかし、全部に IsLabel のインスタンスを定義するのはしんどい

Magic type classes

HasField クラスのインスタンスが自動的に生成されるようになった

import GHC.Records (HasField (getField))

instance HasField "name" Person String where
  getField Person { name } = name

HasField なら IsLabel であるとすればよい

instance HasField x r a => IsLabel x (r -> a) where
  fromLabel = getField @x
:set -XDuplicateRecordFields -XOverloadedLabels
:set -XFlexibleContexts -XFlexibleInstances -XMultiParamTypeClasses

import GHC.OverloadedLabels (IsLabel (fromLabel))
import GHC.Records (HasField (getField))

instance HasField x r a => IsLabel x (r -> a) where
  fromLabel = getField @x

data Person = Person { name :: String }
data Company = Company { name :: String }

let me = Person "Kazuki"
let iij = Company "IIJ"

#name me :: String
#name iij :: String

調べきれていないところ

  • instance HasField x r a => IsLabel x (r -> a) はライブラリー内で書いてしまってよい?
    • 他のライブラリーが同様のインスタンスを定義していたら衝突するのでは?
  • instance HasField x r a => IsLabel x (r -> a) は将来的には標準で定義される?

Record Set Field Proposal

今のところ get しかできないので set もできるようにしよう

class HasField x r a | x r -> a where
  hasField :: r -> (a -> r, a)
-- OR
  getField :: r -> a
  setField :: r -> a -> r

参考

スライドソース

https://github.com/kakkun61/gitpitch/tree/master/gotanda.hs-1

クリエイティブ・コモンズ・ライセンス
岡本和樹(Kazuki Okamoto) 作『レコードのラベルの重複 - Gotanda.hs #1 @HERP』はクリエイティブ・コモンズ 表示 - 非営利 4.0 国際 ライセンスで提供されています。

Note: - 接頭辞が必要

Note: - シンタックスハイライトがくずれている

Note: - `name = name` と書くのはダルい

Note: - 使用時、型注釈がないと `Ambiguous type variable ‘a0’ arising from a use of ‘print’`