lens
ライブラリーの理解を深めようとしているので、それが提供するタイプをいじってみます。私はすでにレンズの使用経験があり、それらがどれほど強力で便利かを知っています。だから私はプリズムに移りましたが、少し迷っています。プリズムは2つのことを可能にしているようです:
最初のポイントは便利に思えますが、通常はエンティティからのすべてのデータを必要とせず、^?
プレーンレンズを使用すると、問題のフィールドが、プリズムの場合と同様に、エンティティが表すブランチに属さない場合にNothing
を取得できます。
2番目のポイント...知りません、用途があるのでしょうか?
質問は、他の光学系ではできないプリズムで何ができるかということです。
編集:素晴らしい回答とリンクをお読みいただきありがとうございます!私はそれらをすべて受け入れることができたらいいのにと思います。
レンズはhas-a関係を特徴付けます;プリズムは、is-a関係を特徴付けます。
_Lens s a
_は、「s
has ana
」; a
から正確に1つのs
を取得し、a
で正確に1つのs
を上書きするメソッドがあります。 _Prism s a
_は、「a
is ans
」です。 a
をs
にアップキャストするメソッドと、s
をa
にダウンキャストする(試行する)メソッドがあります。
その直感をコードに入れると、おなじみの「get-set」(または「costate comonad coalgebra」)レンズの定式化が得られます。
_data Lens s a = Lens {
get :: s -> a,
set :: a -> s -> s
}
_
プリズムの「アップキャスト-ダウンキャスト」表現、
_data Prism s a = Prism {
up :: a -> s,
down :: s -> Maybe a
}
_
up
はa
をs
に(情報を追加せずに)挿入し、down
がs
がa
であるかどうかをテストします。
lens
では、up
は review
で、down
は preview
です。 Prism
コンストラクターはありません。 _prism'
_ smart constructor を使用します。
Prism
で何ができますか?合計タイプの注入と投影!
__Left :: Prism (Either a b) a
_Left = Prism {
up = Left,
down = either Just (const Nothing)
}
_Right :: Prism (Either a b) b
_Right = Prism {
up = Right,
down = either (const Nothing) Just
}
_
レンズはこれをサポートしていません-_get :: Either a b -> a
_を実装できないため、Lens (Either a b) a
を書くことはできません。実際問題として、canはTraversal (Either a b) a
を記述できますが、それはa
から_Either a b
_を作成することはできません-それ ' llは、すでに存在するa
のみを上書きできます。
さておき:
Traversal
sについてのこの微妙な点は、部分レコードフィールドに関する混乱の原因だと思います。プレーンレンズを使用した_
^?
_は、問題のフィールドがエンティティが表すブランチに属していない場合にNothing
を取得できます_
^?
_は、Lens
内で正確に1つのNothing
を識別するため、実際のa
で_Lens s a
_を使用してもs
が返されることはありません。部分的なレコードフィールドに直面したとき、_data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool }
_
makeLenses
は、Traversal
ではなく、Lens
を生成します。_wobble :: Traversal' Wibble Int wubble :: Traversal' Wibble Bool
_
Prism
sを実際に適用する方法の例については、 _Control.Exception.Lens
_ を参照してください。これは、Prism
sのコレクションをHaskellの拡張可能なException
階層に提供します。これにより、SomeException
sでランタイムタイプテストを実行し、SomeException
に特定の例外を挿入できます。
__ArithException :: Prism' SomeException ArithException
_AsyncException :: Prism' SomeException AsyncException
-- etc.
_
(これらは実際の型のわずかに単純化されたバージョンです。実際には、これらのプリズムはオーバーロードされたクラスメソッドです。)
より高いレベルで考えると、特定のプログラム全体は「基本的にPrism
」であると考えることができます。データのエンコードとデコードはその一例です。構造化データをいつでもString
に変換できますが、すべてのString
を解析できるわけではありません。
_showRead :: (Show a, Read a) => Prism String a
showRead = Prism {
up = show,
down = listToMaybe . fmap fst . reads
}
_
要約すると、Lens
esとPrism
sは、オブジェクト指向プログラミング、構成、サブタイプ化の2つのコア設計ツールを一緒にエンコードします。 Lens
esは、Javaの_.
_および_=
_演算子のファーストクラスバージョンであり、Prism
sは、Javaのinstanceof
および暗黙のアップキャストのファーストクラスバージョンです。
Lens
esについて考える有益な方法の1つは、複合s
をフォーカスされた値a
といくつかのコンテキストc
に分割する方法を提供することです。擬似コード:
_type Lens s a = exists c. s <-> (a, c)
_
このフレームワークでは、Prism
を使用すると、s
をa
またはコンテキストc
のいずれかとして見ることができます。
_type Prism s a = exists c. s <-> Either a c
_
(これらは、上記で示した単純な表現と同型であると確信するためにお任せします。これらの型に対してget
/set
/up
/down
を実装してみてください!)
この意味で、Prism
はco -Lens
です。 Either
は_(,)
_のカテゴリカル双対です。 Prism
は、Lens
のカテゴリカルデュアルです。
"profunctor opticals" 定式化でこの二重性を観察することもできます- Strong
および Choice
デュアルです。
_type Lens s t a b = forall p. Strong p => p a b -> p s t
type Prism s t a b = forall p. Choice p => p a b -> p s t
_
これは多かれ少なかれlens
が使用する表現です。これらのLens
esとPrism
sは非常に合成可能であるためです。 Prism
sを構成して、より大きなPrism
sを取得できます( "a
is ans
、whichis a = p
")_(.)
_を使用して、Prism
をLens
で構成すると、Traversal
が得られます。
ブログ記事を書きましたが、これはPrismについての直感を構築するのに役立つかもしれません:プリズムはコンストラクターです(レンズはフィールドです)。 http://oleg.fi/gists/posts/2018-06-19-prisms-are-constructors.html
プリズムは、ファーストクラスパターンマッチングとして導入できますが、それは片側ビューです。私は彼らが一般化されたコンストラクタであると思いますが、実際の構築よりもパターンマッチングに頻繁に使用されます。
コンストラクターの重要な特性(および合法的なプリズム)は、その注入性です。通常のプリズムの法則はそれを直接述べていませんが、単射性は推測できます。
lens
- libraryドキュメントを引用すると、プリズムの法則は次のとおりです。
最初に、review
にPrism
を付けた値を、次にpreview
を付けた場合、それを取得します。
preview l (review l b) ≡ Just b
次に、値Prism
からl
s
を使用して値aを抽出できる場合、値s
はl
およびa
:
preview l s ≡ Just a ⇒ review l a ≡ s
実際、最初の法則だけで、Prism
を介して構築の単射性を証明できます。
review l x ≡ review l y ⇒ x ≡ y
証明は簡単です。
review l x ≡ review l y
-- x ≡ y -> f x ≡ f y
preview l (review l x) ≡ preview l (review l y)
-- rewrite both sides with the first law
Just x ≡ Just y
-- injectivity of Just
x ≡ y
等式推論ツールボックスの追加ツールとして単射性プロパティを使用できます。または、何かが合法なPrism
であるかどうかを判断するための簡単なプロパティとして使用できます。 review
のPrism
側のみであるため、チェックは簡単です。多くのスマートコンストラクターは、たとえば入力データを正規化しますが、合法的なプリズムではありません。
case-insensitive
を使用した例:
-- Bad!
_CI :: FoldCase s => Prism' (CI s) s
_CI = prism' ci (Just . foldedCase)
λ> review _CI "FOO" == review _CI "foo"
True
λ> "FOO" == "foo"
False
最初の法律にも違反しています。
λ> preview _CI (review _CI "FOO")
Just "foo"
他の優れた答えに加えて、Iso
sがこの問題を検討するための素晴らしい視点を提供すると感じています。
いくつかのi :: Iso' s a
は、s
値を持っている場合、(実質的に)a
値も持っていることを意味します。 Iso'
は2つの変換関数、view i :: s -> a
およびreview i :: a -> s
成功とロスレスの両方が保証されています。
いくつかのl :: Lens' s a
は、s
がある場合はa
もあることを意味します。ただし、その逆はありません。 view l :: s -> a
は、変換がロスレスである必要がないため、途中で情報をドロップする可能性があります。したがって、a
(cf. set l :: a -> s -> s
、不足している情報を提供するためにs
値に加えてa
も必要です)。
p :: Prism' s a
は、s
値がある場合、mightもa
を持つが、保証はないことを意味します。変換preview p :: s -> Maybe a
は成功するとは限りません。それでも、あなたは他の方向を持っています、review p :: a -> s
。つまり、Iso
は可逆的であり、常に成功します。可逆性の要件を削除すると、Lens
が得られます。成功の保証を放棄すると、Prism
が得られます。両方をドロップすると、 affine traversal (lensに別のタイプとして存在しない)を取得し、さらに一歩進んで、Traversal
で終わるターゲットを1つだけ持つことをあきらめます。 lensサブタイプ階層 のひし形の1つに反映されています:
Traversal
/ \
/ \
/ \
Lens Prism
\ /
\ /
\ /
Iso