web-dev-qa-db-ja.com

プリズムとは何ですか?

lensライブラリーの理解を深めようとしているので、それが提供するタイプをいじってみます。私はすでにレンズの使用経験があり、それらがどれほど強力で便利かを知っています。だから私はプリズムに移りましたが、少し迷っています。プリズムは2つのことを可能にしているようです:

  1. エンティティが和タイプの特定のブランチに属しているかどうかを判断し、属している場合は、タプルまたはシングルトンの基になるデータをキャプチャします。
  2. エンティティを分解および再構築し、おそらく処理中に変更します。

最初のポイントは便利に思えますが、通常はエンティティからのすべてのデータを必要とせず、^?プレーンレンズを使用すると、問題のフィールドが、プリズムの場合と同様に、エンティティが表すブランチに属さない場合にNothingを取得できます。

2番目のポイント...知りません、用途があるのでしょうか?

質問は、他の光学系ではできないプリズムで何ができるかということです。

編集:素晴らしい回答とリンクをお読みいただきありがとうございます!私はそれらをすべて受け入れることができたらいいのにと思います。

32
Michail

レンズはhas-a関係を特徴付けます;プリズムは、is-a関係を特徴付けます。

_Lens s a_は、「shas ana」; aから正確に1つのsを取得し、aで正確に1つのsを上書きするメソッドがあります。 _Prism s a_は、「ais ans」です。 asにアップキャストするメソッドと、saにダウンキャストする(試行する)メソッドがあります。

その直感をコードに入れると、おなじみの「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
}
_

upasに(情報を追加せずに)挿入し、downsaであるかどうかをテストします。

lensでは、upreview で、downpreview です。 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を書くことはできません。実際問題として、canTraversal (Either a b) aを記述できますが、それはaから_Either a b_を作成することはできません-それ ' llは、すでに存在するaのみを上書きできます。

さておき:Traversalsについてのこの微妙な点は、部分レコードフィールドに関する混乱の原因だと思います。

プレーンレンズを使用した_^?_は、問題のフィールドがエンティティが表すブランチに属していない場合に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
_

Prismsを実際に適用する方法の例については、 _Control.Exception.Lens_ を参照してください。これは、PrismsのコレクションをHaskellの拡張可能なException階層に提供します。これにより、SomeExceptionsでランタイムタイプテストを実行し、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
}
_

要約すると、LensesとPrismsは、オブジェクト指向プログラミング、構成、サブタイプ化の2つのコア設計ツールを一緒にエンコードします。 Lensesは、Javaの_._および_=_演算子のファーストクラスバージョンであり、Prismsは、Javaのinstanceofおよび暗黙のアップキャストのファーストクラスバージョンです。


Lensesについて考える有益な方法の1つは、複合sをフォーカスされた値aといくつかのコンテキストcに分割する方法を提供することです。擬似コード:

_type Lens s a = exists c. s <-> (a, c)
_

このフレームワークでは、Prismを使用すると、saまたはコンテキストcのいずれかとして見ることができます。

_type Prism s a = exists c. s <-> Either a c
_

(これらは、上記で示した単純な表現と同型であると確信するためにお任せします。これらの型に対してget/set/up/downを実装してみてください!)

この意味で、Prismco -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が使用する表現です。これらのLensesとPrismsは非常に合成可能であるためです。 Prismsを構成して、より大きなPrismsを取得できます( "ais ans、whichis a = p ")_(.)_を使用して、PrismLensで構成すると、Traversalが得られます。

31

ブログ記事を書きましたが、これはPrismについての直感を構築するのに役立つかもしれません:プリズムはコンストラクターです(レンズはフィールドです)。 http://oleg.fi/gists/posts/2018-06-19-prisms-are-constructors.html


プリズムは、ファーストクラスパターンマッチングとして導入できますが、それは片側ビューです。私は彼らが一般化されたコンストラクタであると思いますが、実際の構築よりもパターンマッチングに頻繁に使用されます。

コンストラクターの重要な特性(および合法的なプリズム)は、その注入性です。通常のプリズムの法則はそれを直接述べていませんが、単射性は推測できます。

lens- libraryドキュメントを引用すると、プリズムの法則は次のとおりです。

最初に、reviewPrismを付けた値を、次にpreviewを付けた場合、それを取得します。

preview l (review l b) ≡ Just b

次に、値Prismからlsを使用して値aを抽出できる場合、値slおよび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であるかどうかを判断するための簡単なプロパティとして使用できます。 reviewPrism側のみであるため、チェックは簡単です。多くのスマートコンストラクターは、たとえば入力データを正規化しますが、合法的なプリズムではありません。

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"
14
phadej

他の優れた答えに加えて、Isosがこの問題を検討するための素晴らしい視点を提供すると感じています。

  • いくつかの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値がある場合、mightaを持つが、保証はないことを意味します。変換preview p :: s -> Maybe aは成功するとは限りません。それでも、あなたは他の方向を持っています、review p :: a -> s

つまり、Isoは可逆的であり、常に成功します。可逆性の要件を削除すると、Lensが得られます。成功の保証を放棄すると、Prismが得られます。両方をドロップすると、 affine traversallensに別のタイプとして存在しない)を取得し、さらに一歩進んで、Traversalで終わるターゲットを1つだけ持つことをあきらめます。 lensサブタイプ階層 のひし形の1つに反映されています:

 Traversal
    / \
   /   \
  /     \
Lens   Prism
  \     /
   \   /
    \ /
    Iso
10
duplode