2

私はレンズに関するチュートリアルを読んでいます。導入部で、著者は、lens標準の Haskell を使用して OOP スタイルの「セッター」/「ゲッター」を実装する方法のいくつかの例を示して、コンセプトの動機付けをしています。次の例で混乱しています。

User図 1 (下記) のように代数データ型を定義するとしましょう。NaiveLensチュートリアルでは、データ型と関数を使用して "セッター" 機能を実装できると (正しく) 述べていnameLensます (図 1 も参照)。使用例を図 2 に示します。

次の(やや明白な)関数が同じようにうまく機能するように見えるのに、なぜ「セッター」機能を実装するためにそのような精巧な構造(つまり、NaiveLensデータ型と関数)が必要なのか、私は困惑しています。 nameLensset' a s = s {name = a}

ただし、私の「明白な」関数が の一部であるラムダ関数に他ならないことを考えると、nameLens以下の構成を使用することには確かに利点があると思いますが、密度が高すぎてその利点が何であるかを確認できません。Haskell ウィザードの 1 人が理解を助けてくれることを願っています。

図 1 (定義):

data User = User { name :: String
                 , age :: Int
                 } deriving Show

data NaiveLens s a = NaiveLens { view :: s -> a
                               , set :: a -> s -> s
                               }

nameLens :: NaiveLens User String
nameLens = NaiveLens name (\a s -> s {name = a})

図 2 (使用例):

λ: let john = User {name="John",age=30}
john :: User

λ: set nameLens "Bob" john
User {name = "Bob", age = 30}
it :: User
4

2 に答える 2

6

レンズの主な利点は、 を構成することです。そのため、ネストされたレコードのフィールドへのアクセスと更新に使用できます。レコード更新構文を使用してこの種のネストされた更新を手動で記述するのは、すぐに面倒になります。

于 2014-11-19T19:59:17.117 に答える
2

Emailデータ型を追加したとします:

data Email = Email
    { _handle :: String
    , _domain :: String
    } deriving (Eq, Show)

handle :: NaiveLens Email String
handle = NaiveLens _handle (\h e -> e { _handle = h })

そして、これをフィールドとしてUserタイプに追加しました:

data User = User
    { _name :: String
    , _age :: Int
    , _userEmail :: Email
    } deriving (Eq, Show)

email :: NaiveLens User Email
email = NaiveLens _userEmail (\e u -> u { _userEmail = e })

レンズの本当の力は、それらを構成できることにありますが、これは少しトリッキーなステップです. 次のような関数が必要です

(...) :: NaiveLens s b -> NaiveLens b a -> NaiveLens s a
NaiveLens viewA setA ... NaiveLens viewB setB
    = NaiveLens (viewB . viewA) (\c a -> setA (setB c (viewA a)) a)

これがどのように書かれたかの説明については、恥知らずに引用したこの投稿を参照してください。この新しいレンズの結果のsetフィールドは、新しい値とトップレベルのレコードを取得し、下位のレコードを検索してその値をcに設定し、その新しいレコードをトップレベルのレコードに設定すると考えることができます。

これで、レンズを構成するための便利な関数ができました。

> let bob = User "Bob" 30 (Email "bob" "gmail")
> view (email...handle) bob
"bob"
> set (email...handle) "NOTBOB" bob
User {_name = "Bob", _age = 30, _userEmail = Email {_handle = "NOTBOB", _domain = "gmail"}}

ここでは合成演算子として使用しました。これは入力がかなり簡単で、演算子...に似ていると思うからです。.これにより、構造体にドリルダウンして、かなり任意に値を取得および設定する方法が得られます。同様に作成されたレンズがあればdomain、ほとんど同じ方法でその値を取得および設定できます。これが、単純に手の込んだ関数合成であっても、OOP メンバー アクセスのように見える理由です。

レンズライブラリ (私のレンズの選択) を見ると、テンプレート haskell を使用してレンズを自動的に構築するための優れたツールがいくつかあります.。カスタムの。

于 2014-11-19T20:54:45.353 に答える