ここに、あなたの美しいアイデアを広く支持する議論があります。
パート1:mapMaybe
ここでの私の計画は、問題をの観点から再説明することであり、mapMaybe
そうすることで私たちがより身近な立場に立つことを期待しています。そのために、いくつかのEither
-jugglingユーティリティ関数を使用します。
maybeToRight :: a -> Maybe b -> Either a b
rightToMaybe :: Either a b -> Maybe b
leftToMaybe :: Either a b -> Maybe a
flipEither :: Either a b -> Either b a
(私は最初から3人の名前を取っrelude、およびから4番目のエラー。ところで、エラーの提供maybeToRight
とrightToMaybe
としてnote
とhush
で、それぞれControl.Error.Util
。)
お気づきのとおり、mapMaybe
は次のように定義できますpartition
。
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
mapMaybe f = snd . partition . fmap (maybeToRight () . f)
重要なのは、他の方法で回避することもできます。
partition :: Filterable f => f (Either a b) -> (f a, f b)
partition = mapMaybe leftToMaybe &&& mapMaybe rightToMaybe
これは、の観点から法律を作り直すことが理にかなっていることを示唆していますmapMaybe
。アイデンティティ法では、そうすることで、次のことを完全に忘れることができますtrivial
。
-- Left and right unit
mapMaybe rightToMaybe . fmap (bwd elunit) = id -- [I]
mapMaybe leftToMaybe . fmap (bwd erunit) = id -- [II]
連想性については、を使用rightToMaybe
しleftToMaybe
て、法則を3つの方程式に分割することができます。1つは、連続するパーティションから取得する各コンポーネントに対応します。
-- Associativity
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe leftToMaybe -- [V]
パラメトリック性の手段mapMaybe
はEither
、ここで扱っている値に関して不可知論的です。そのため、Either
同型写像の小さな武器を使用して、周囲をシャッフルし、[I]が[II]と同等であり、[III]が[V]と同等であることを示すことができます。これで3つの方程式になりました。
mapMaybe rightToMaybe . fmap (bwd elunit) = id -- [I]
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
パラメトリック性によりfmap
、[I] を飲み込むことができます。
mapMaybe (rightToMaybe . bwd elunit) = id
しかし、それは単に...
mapMaybe Just = id
...これは、witherableのFilterable
保護/同一性法に相当します。
mapMaybe (Just . f) = fmap f
これFilterable
には合成法もあります:
-- The (<=<) is from the Maybe monad.
mapMaybe g . mapMaybe f = mapMaybe (g <=< f)
私たちの法律からこれを導き出すこともできますか?[III]から始めましょう。もう一度、パラメトリック性が機能します。これはトリッキーなので、完全に書き留めます。
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
-- f :: a -> Maybe b; g :: b -> Maybe c
-- Precomposing fmap (right (maybeToRight () . g) . maybeToRight () . f)
-- on both sides:
mapMaybe rightToMaybe . fmap (bwd eassoc)
. fmap (right (maybeToRight () . g) . maybeToRight () . f)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe
. fmap (right (maybeToRight () . g) . maybeToRight () . f)
mapMaybe rightToMaybe . mapMaybe rightToMaybe
. fmap (right (maybeToRight () . g) . maybeToRight () . f) -- RHS
mapMaybe rightToMaybe . fmap (maybeToRight () . g)
. mapMaybe rightToMaybe . fmap (maybeToRight () . f)
mapMaybe (rightToMaybe . maybeToRight () . g)
. mapMaybe (rightToMaybe . maybeToRight () . f)
mapMaybe g . mapMaybe f
mapMaybe rightToMaybe . fmap (bwd eassoc)
. fmap (right (maybeToRight () . g) . maybeToRight () . f) -- LHS
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight () . g) . maybeToRight () . f)
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight ()) . maybeToRight () . fmap @Maybe g . f)
-- join @Maybe
-- = rightToMaybe . bwd eassoc . right (maybeToRight ()) . maybeToRight ()
mapMaybe (join @Maybe . fmap @Maybe g . f)
mapMaybe (g <=< f) -- mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
反対方向:
mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
-- f = rightToMaybe; g = rightToMaybe
mapMaybe (rightToMaybe <=< rightToMaybe)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe
mapMaybe (rightToMaybe <=< rightToMaybe) -- LHS
mapMaybe (join @Maybe . fmap @Maybe rightToMaybe . rightToMaybe)
-- join @Maybe
-- = rightToMaybe . bwd eassoc . right (maybeToRight ()) . maybeToRight ()
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight ()) . maybeToRight ()
. fmap @Maybe rightToMaybe . rightToMaybe)
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight () . rightToMaybe)
. maybeToRight () . rightToMaybe)
mapMaybe (rightToMaybe . bwd eassoc) -- See note below.
mapMaybe rightToMaybe . fmap (bwd eassoc)
-- mapMaybe rightToMaybe . fmap (bwd eassoc)
-- = mapMaybe rightToMaybe . mapMaybe rightToMaybe
(注:maybeToRight () . rightToMaybe :: Either a b -> Either () b
はそうid
ではありませんが、上記の導出では左の値はとにかく破棄されるため、あたかもそうであるかのように取り消しid
ます。)
したがって、[III]はwitherableの合成法に相当しFilterable
ます。
この時点で、構成法を使用して[IV]を処理できます。
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
mapMaybe (rightToMaybe <=< leftToMaybe) . fmap (bwd eassoc)
= mapMaybe (letfToMaybe <=< rightToMaybe)
mapMaybe (rightToMaybe <=< leftToMaybe . bwd eassoc)
= mapMaybe (letfToMaybe <=< rightToMaybe)
-- Sufficient condition:
rightToMaybe <=< leftToMaybe . bwd eassoc = letfToMaybe <=< rightToMaybe
-- The condition holds, as can be directly verified by substiuting the definitions.
これは、クラスの量が十分に確立されたの定式化であることを示すのに十分Filterable
です。これは非常に良い結果です。ここに法律の要約があります:
mapMaybe Just = id -- Identity
mapMaybe g . mapMaybe f = mapMaybe (g <=< f) -- Composition
以下のようwitherableドキュメントは注意し、これらはからファンクタのためのファンクタの法律ですたぶんKleisliにHask。
パート2:オルタナティブとモナド
これで、代替モナドに関する実際の質問に取り組むことができます。の提案された実装partition
は:
partitionAM :: (Alternative f, Monad f) => f (Either a b) -> (f a, f b)
partitionAM
= (either return (const empty) =<<) &&& (either (const empty) return =<<)
私のより広範な計画に従って、私はmapMaybe
プレゼンテーションに切り替えます:
mapMaybe f
snd . partition . fmap (maybeToRight () . f)
snd . (either return (const empty) =<<) &&& (either (const empty) return =<<)
. fmap (maybeToRight () . f)
(either (const empty) return =<<) . fmap (maybeToRight () . f)
(either (const empty) return . maybeToRight . f =<<)
(maybe empty return . f =<<)
そして、次のように定義できます。
mapMaybeAM :: (Alternative f, Monad f) => (a -> Maybe b) -> f a -> f b
mapMaybeAM f u = maybe empty return . f =<< u
または、ポイントフリースペルでは:
mapMaybeAM = (=<<) . (maybe empty return .)
上記の数段落で、私はFilterable
法律がそれmapMaybe
がKleisli MaybeからHaskへのファンクターの射の写像であると言うことに注意しました。ファンクタの組成のでファンクタであり、そして(=<<)
よりファンクタの射マッピングあるKleisli FにHask、(maybe empty return .)
からファンクタの射マッピングされるかもしれKleisliにfをKleisliで十分mapMaybeAM
合法することができます。関連するファンクタの法則は次のとおりです。
maybe empty return . Just = return -- Identity
maybe empty return . g <=< maybe empty return . f
= maybe empty return . (g <=< f) -- Composition
この同一性法が成立するので、構成法に焦点を当てましょう。
maybe empty return . g <=< maybe empty return . f
= maybe empty return . (g <=< f)
maybe empty return . g =<< maybe empty return (f a)
= maybe empty return (g =<< f a)
-- Case 1: f a = Nothing
maybe empty return . g =<< maybe empty return Nothing
= maybe empty return (g =<< Nothing)
maybe empty return . g =<< empty = maybe empty return Nothing
maybe empty return . g =<< empty = empty -- To be continued.
-- Case 2: f a = Just b
maybe empty return . g =<< maybe empty return (Just b)
= maybe empty return (g =<< Just b)
maybe empty return . g =<< return b = maybe empty return (g b)
maybe empty return (g b) = maybe empty return (g b) -- OK.
したがって、mapMaybeAM
合法的なIFFであるmaybe empty return . g =<< empty = empty
すべてのためにg
。ここで、としてempty
定義されている場合absurd <$> nil ()
、ここで行ったように、次のことを証明できf =<< empty = empty
ますf
。
f =<< empty = empty
f =<< empty -- LHS
f =<< absurd <$> nil ()
f . absurd =<< nil ()
-- By parametricity, f . absurd = absurd, for any f.
absurd =<< nil ()
return . absurd =<< nil ()
absurd <$> nil ()
empty -- LHS = RHS
直感的に、empty
が本当に空である場合(当然のことながら、ここで使用している定義を前提としています)、f
適用されるの値f =<< empty
がないため、結果は以外になりempty
ます。
ここでの別のアプローチは、Alternative
とMonad
クラスの相互作用を調べることです。たまたま、代替モナドのクラスがありますMonadPlus
。したがって、スタイルを変更mapMaybe
すると次のようになります。
-- Lawful iff, for any f, mzero >>= maybe empty mzero . f = mzero
mmapMaybe :: MonadPlus m => (a -> Maybe b) -> m a -> m b
mmapMaybe f m = m >>= maybe mzero return . f
どの法律のセットが最も適切であるかについてはさまざまな意見がありますがMonadPlus
、誰も反対しないと思われる法律の1つは...
mzero >>= f = mzero -- Left zero
...これは、empty
上記のいくつかの段落で説明していた特性です。の合法性はmmapMaybe
、左ゼロ法からすぐに従う。
(ちなみに、Control.Monad
はmfilter :: MonadPlus m => (a -> Bool) -> m a -> m a
、をfilter
使用して定義できると一致しますmmapMaybe
。)
要約すれば:
しかし、この実装は常に合法ですか?時々合法ですか(「時々」の正式な定義の場合)?
はい、実装は合法です。この結論は、empty
実際に空である必要があるか、左ゼロMonadPlus
法に従う関連する代替モナドに依存しますが、結局はほぼ同じことになります。
次の反例で説明できるように、これはにFilterable
含まれていないことを強調する価値MonadPlus
があります。
ZipList
:フィルタリング可能ですが、モナドではありません。Filterable
インスタンスはAlternative
異なりますが、リストのインスタンスと同じです。
Map
:フィルタリング可能ですが、モナドでも適用可能でもありません。実際、Map
の賢明な実装がないため、適用できませんpure
。ただし、独自のものがありempty
ます。
MaybeT f
:インスタンスMonad
とAlternative
インスタンスはf
モナドであるempty
必要があり、分離された定義では少なくともが必要ですがApplicative
、Filterable
インスタンスのみが必要ですFunctor f
(Maybe
レイヤーをその中に挿入すると、すべてがフィルター可能になります)。
パート3:空
この時点では、どれほどの役割empty
、またはnil
、実際に果たしているのか、まだ疑問に思われるかもしれませんFilterable
。これはクラスメソッドではありませんが、ほとんどのインスタンスは賢明なバージョンを持っているように見えます。
確信できることの1つは、フィルター可能な型に居住者がいる場合、少なくとも1つは空の構造になるということです。
chop :: Filterable f => f a -> f Void
chop = mapMaybe (const Nothing)
の存在はchop
、単一の nil
空の値が存在すること、またはchop
常に同じ結果が得られることを意味するわけではありません。たとえば、MaybeT IO
そのFilterable
インスタンスがIO
計算結果を検閲する方法と見なされる可能性があるを考えてみます。インスタンスは完全に合法ですが、任意の効果をもたらすchop
個別のMaybeT IO Void
値を生成できますIO
。
最後のノートでは、あなたがしているに言及したように、強いmonoidalファンクタでの作業の可能性Alternative
とFilterable
することによってリンクされているunion
/ partition
とnil
/ trivial
同型。持つunion
とpartition
相互の逆数として考えられるが、かなり与えられ、制限することunion . partition
を破棄インスタンスの大きなシェアのための要素の配置に関するいくつかの情報。他の同型に関してtrivial . nil
は、取るに足らないことですが、インスタンスのかなりの割合を保持nil . trivial
する単一のf Void
値のみが存在することを意味するという点で興味深いFilterable
です。MonadPlus
この状態のバージョンがあることがあります。私たちがそれを要求した場合、いずれかのためにu
...
absurd <$> chop u = mzero
...そして、mmapMaybe
パート2のを置き換えると、次のようになります。
absurd <$> chop u = mzero
absurd <$> mmapMaybe (const Nothing) u = mzero
mmapMaybe (fmap absurd . const Nothing) u = mzero
mmapMaybe (const Nothing) u = mzero
u >>= maybe mzero return . const Nothing = mzero
u >>= const mzero = mzero
u >> mzero = mzero
この特性はの右ゼロ法として知られてMonadPlus
いますが、その特定のクラスの法としての地位に異議を唱えるのには十分な理由があります。