残りのフィールドをコピーしながら、レコード内の単一のフィールドを割り当てる簡単な方法は?


119

次のレコードADTがあるとします。

data Foo = Bar { a :: Integer, b :: String, c :: String }

次のように、レコードを取り、1つのフィールドを除くすべてのフィールドが引数として渡されたものと同一の値を持っている(同じタイプの)レコードを返す関数が必要です。

walkDuck x = Bar { a = a x, b = b x, c = lemonadeStand (a x) (b x) }

上記は10機能しますが、より多くのフィールド(たとえば)を持つレコードの場合、そのような関数を作成すると、多くの入力が必要になるので、かなり不要です。

同じことをする面倒な方法はありますか?


3
更新のためのレコード構文は存在しますが、すぐに面倒になります。代わりにレンズを見てください。
Cat Plus Plus

回答:


155

はい、レコードフィールドを更新する良い方法があります。GHCiでできること-

> data Foo = Foo { a :: Int, b :: Int, c :: String }  -- define a Foo
> let foo = Foo { a = 1, b = 2, c = "Hello" }         -- create a Foo
> let updateFoo x = x { c = "Goodbye" }               -- function to update Foos
> updateFoo foo                                       -- update the Foo
Foo {a = 1, b = 2, c = "Goodbye" }

9
RecordWildCards拡張子はスコープで「アンパック」フィールドに、同様に素晴らしいことができます。アップデートに関しては、それほど良くはありません:incrementA x@Foo{..} = x { a = succ a }
Jon Purdy '19

2
ところで、Frege(JVMのHaskell)では、関数を次のように定義しますupdateFoo x = x.{ c = "Goodbye" }.演算子に注意してください)。
0dB


ありがとう。悲しいことに、Haskellを書いてから久しぶりです!
Chris Taylor、

37

これはレンズに最適です。

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

次に:

setL c "Goodbye" test

「test」のフィールド「c」を文字列に更新します。


5
また、レンズのようなパッケージでは、フィールドを取得および設定するための関数に加えて、演算子を定義することがよくあります。たとえば、iircをtest $ c .~ "Goodbye"どのようlensに実行するかです。これは直感的だと言っているのではありませんが、演算子を知っていれば、と同じくらい簡単に来ると思い$ます。
Thomas M. DuBuisson 2013

3
setLがどこに行ったか知っていますか?Control.Lensをインポートしていますが、ghcはsetLが未定義であることを報告しています。
dbanas 2017

1
setLの代わりにsetを使用
Subhod I

16

補助機能を定義したり、レンズを使用したりする必要はありません。標準のHaskellにはすでに必要なものが用意されています。ドンスチュワートの例を見てみましょう。

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

次にtest { c = "Goodbye" }、更新されたレコードを取得するように言うことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.