14

私はHaskellに比較的慣れておらず、バイファンクターの有用性を理解するのに苦労しています。理論的には理解していると思います。たとえば、Either や Maybe など、複数の具体的な型を抽象化する型にまたがってマッピングしたい場合は、それらを bifunctor にカプセル化する必要があります。しかし一方では、これらの例は特に工夫されているように見えますが、他方では、構成によって同じ機能を簡単に実現できるように見えます。

例として、 Jeremy Gibbons と Bruno C によるThe Essence of the Iterator Pattern でこのコードに出くわしました。S. オリベイラ:

import Data.Bifunctor

data Fix s a = In {out::s a (Fix s a) }

map' :: Bifunctor s => (a -> b) -> Fix s a -> Fix s b
map' f = In . bimap f (map' f) . out

fold' :: Bifunctor s => (s a b -> b) -> Fix s a -> b
fold' f = f . bimap id (fold' f) . out

unfold' :: Bifunctor s => (b -> s a b) -> b -> Fix s a
unfold' f = In . bimap id (unfold' f) . f

ポイントは、マッピング関数と折りたたみ関数を構成してイテレータ パターンを作成することであると理解しています。これは、2 つのパラメーターを必要とするデータ コンストラクターを定義することによって実現されます。しかし、実際には、通常のファンクターを使用し、bimap の代わりに fmap を使用して関数を構成する場合と、これがどのように違うのかわかりません。この例と、おそらく一般的なものの両方で、明らかに何かが欠けているに違いないと思います。

4

1 に答える 1

21

少し考え過ぎだと思います。バイファンクターは、2 パラメーター ファンクターのようなものです。Gibbons と Oliveira のアイデアは、bifanctor の 1 つのアプリケーションにすぎません。ちょうど標準的な再帰スキームの動物園が functor の 1 つのアプリケーションにすぎないのと同じです。

class Bifunctor f where
    bimap :: (a -> c) -> (b -> d) -> f a b -> f c d

Bifunctors には種類が* -> * -> *あり、両方のパラメーターを共変的にマッピングできます。これを、共変的にマッピングできるFunctorパラメーター ( ) が 1 つしかない通常の s と比較してください。f :: * -> *

たとえば、Eitherの通常のFunctorインスタンスについて考えてみましょう。fmap2 番目の型パラメータのみを許可します。Right値はマップされ、Left値はそのままになります。

instance Functor (Either a) where
    fmap f (Left x) = Left x
    fmap f (Right y) = Right (f y)

ただし、そのBifunctorインスタンスを使用すると、合計の両方の半分をマップできます。

instance Bifunctor Either where
    bimap f g (Left x) = Left (f x)
    bimap f g (Right y) = Right (g y)

同様に、タプルの場合:(,)Functorインスタンスでは、2 番目のコンポーネントのみをマップBifunctorできますが、両方の部分をマップできます。

instance Functor ((,) a) where
    fmap f (x, y) = (x, f y)

instance Bifunctor (,) where
    bimap f g (x, y) = (f x, g y)

Maybeあなたが言及した は、パラメーターが1つしかないため、バイファンクターのフレームワークに適合しないことに注意してください。


の問題でFixは、バイファンクターの固定小数点により、ほとんどのコンテナーのような構造など、関数型パラメーターを持つ再帰型を特徴付けることができます。例としてリストを使用してみましょう。

newtype Fix f = Fix { unFix :: f (Fix f) }

data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)

上記のように、標準の functorial を使用すると、 forのパラメーターについて何も知らないため、forFixのインスタンスの一般的な派生はありません。つまり、種類が間違っているため、ようなものを書くことはできません。おそらく次を使用して、 for リストを手動でクランクする必要があります。FunctorListFixListainstance Something f => Functor (Fix f)Fixmapcata

map :: (a -> b) -> List a -> List b
map f = cata phi
    where phi Nil_ = Fix Nil_
          phi Cons_ x r = Fix $ Cons_ (f x) r

の二関数バージョンでFixは、 のインスタンスが許可されますFunctorFixbifunctor のパラメーターの 1 つを使用して再帰的な出現をプラグインしFix f a、もう 1 つを結果のデータ型の functorial パラメーターの代わりに使用します。

newtype Fix f a = Fix { unFix :: f a (Fix f a) }

instance Bifunctor f => Functor (Fix f) where
    fmap f = Fix . bimap f (fmap f) . unFix

したがって、次のように記述できます。

deriveBifunctor ''ListF

type List = Fix ListF

Functor無料でインスタンスを取得します。

map :: (a -> b) -> List a -> List b
map = fmap

もちろん、複数のパラメーターを持つ再帰構造を一般的に使用したい場合は、tri-functor、quad-functor などに一般化する必要があります...これは明らかに持続可能ではなく、多くの作業が必要です (より高度なプログラミングでは言語) は、型を特徴付けるためのより柔軟なシステムの設計に投入されています。

于 2016-12-10T12:55:27.053 に答える