Haskell Language
レコードの構文
サーチ…
基本的な構文
レコードは、フィールドに名前を付けることができるような和代数data
型の拡張です。
data StandardType = StandardType String Int Bool --standard way to create a sum type
data RecordType = RecordType { -- the same sum type with record syntax
aString :: String
, aNumber :: Int
, isTrue :: Bool
}
フィールド名を使用して、名前付きフィールドをレコードから取得することができます
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
レコードはパターンマッチングすることができます
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
すべてのフィールドに名前を付ける必要はないことに注意してください
レコードはフィールドの名前を付けて作成されますが、通常の合計タイプとして作成することもできます(フィールドの数が少なく、変更されにくい場合に便利です)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
レコードが名前付きフィールドなしで作成された場合、コンパイラは警告を発行し、結果の値はundefined
なります。
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
レコードのフィールドは、その値を設定することによって更新できます。記載されていないフィールドは変更されません。
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
複雑なレコードタイプのレンズを作成することはしばしば役に立ちます。
フィールド値の変更中にレコードをコピーする
このタイプがあるとします:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
2つの値:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
alex
からコピーして変更する値を指定することで、 Person
型の新しい値を作成できます。
anotherAlex = alex { age = 31 }
alex
とanotherAlex
の値はanotherAlex
ようになります。
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
新しいタイプのレコード
newtype
ではレコード構文を使用できますが、正確に1つのフィールドを持つコンストラクタが1つだけ存在するという制限があります。ここでの利点は、newtypeをラップ解除する関数の自動作成です。これらのフィールドは、モナドの場合はrun
、モノイドの場合はget
、その他のタイプの場合はun
で始まることがよくあります。
newtype State s a = State { runState :: s -> (s, a) }
newtype Product a = Product { getProduct :: a }
newtype Fancy = Fancy { unfancy :: String }
-- a fancy string that wants to avoid concatenation with ordinary strings
レコード構文は通常、値を形成するために使用されることはなく、フィールド名はアンラッピングに厳密に使用されることに注意することが重要です
getProduct $ mconcat [Product 7, Product 9, Product 12]
-- > 756
RecordWildCards
{-# LANGUAGE RecordWildCards #-}
data Client = Client { firstName :: String
, lastName :: String
, clientID :: String
} deriving (Show)
printClientName :: Client -> IO ()
printClientName Client{..} = do
putStrLn firstName
putStrLn lastName
putStrLn clientID
Client{..}
パターンは、コンストラクタのClient
すべてのフィールドをスコープ内に持ち込み、パターンと同じです
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
また、他のフィールドマッチャーと組み合わせることもできます:
Client { firstName = "Joe", .. }
これは
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
フィールドラベルを使用したデータ型の定義
フィールドラベルを使用してデータ型を定義することは可能です。
data Person = Person { age :: Int, name :: String }
この定義は、データ型の一部にアクセスするために使用できるレコードアクセサも定義しているため、通常のレコード定義とは異なります。
この例では、2つのレコードアクセサは、規定されているage
とname
私たちがアクセスすることを可能にする、 age
とname
をそれぞれのフィールドを。
age :: Person -> Int
name :: Person -> String
レコードアクセサは、コンパイラによって自動的に生成されるHaskell関数です。したがって、それらは通常のHaskell関数のように使用されます。
フィールドに名前を付けることによって、コードを読みやすくするために、フィールドラベルを他のコンテキストで使用することもできます。
パターンマッチング
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
パターンマッチング中の関連フィールドラベルの位置にある値を、定義のRHSで使用できる新しい値(この場合はx
)にバインドすることができます。
NamedFieldPuns
とのパターンマッチング
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
代わりに、 NamedFieldPuns
拡張では、照合したいフィールドラベルを指定するだけで、この名前は定義のRHSでシャドーされるので、 name
はレコードアクセサではなく値を参照します。
RecordWildcards
パターンマッチング
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
RecordWildCards
を使用して一致さRecordWildCards
、すべてのフィールドラベルがスコープに入れられます。 (この特定の例では、 name
とage
)
この拡張は、 Person
の定義が不明な場合に値がどのようにスコープに入れられるかが明確でないため、少し議論の余地があります。
更新を記録する
setName :: String -> Person -> Person
setName newName person = person { name = newName }
フィールド・ラベルを使用してデータ型を更新するための特別な構文もあります。