我有代码(实际上是在C#中,但是这个问题与C#没有任何关系,所以我会谈到我在Haskell中说的所有类型)我在Either a b
里面工作。然后我bind
一个带有签名的函数,在Haskell中说是b -> (c, d)
,之后我想将c
拉到外面并在左边的情况下默认它,即我想要(c, Either a d)
。现在这种模式多次出现在我写的一个特定服务中,所以我提出了一种方法来实现它。然而,每当我在没有理解正确的理论基础的情况下“弥补”这样的方法时,它就会让我感到困扰。换句话说,我们在这里处理什么抽象?
我在一些F#代码中有类似的情况,我的对和我的任何一方都被颠倒了:(a, b) -> (b -> Either c d) -> Either c (a, d)
。我问朋友这是什么,他让我转向traverse,这让我非常高兴,尽管由于缺乏类型类,我必须在F#中进行可怕的单态实现。 (我希望我可以将我在Visual Studio中的F1重新映射到Hackage;它是我编写.NET代码的主要资源之一)。但问题是遍历是:
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
这意味着当你开始使用一对并希望“绑定”其中任何一个时,它的效果很好,但是当你开始使用一个并且想要最终得到一对时它不起作用,因为pair不是Applicative。
然而,我更多地考虑了我的第一个案例,那个不是traverse
,并且意识到“在左边案例中默认c
”只能通过映射左边的情况来完成,这会将问题改为具有这样的形状:Either (c, a) (c, d) -> (c, Either a d)
我认识到我们在算术中用乘法和加法看到的模式:a(b + c) = ab + ac
。我还记得布尔代数和集合理论中存在相同的模式(如果内存服务,A intersect (B union C) = (A intersect B) union (A intersect C)
)。显然,这里有一些抽象的代数结构。但是,内存不起作用,我不记得它被称为什么。维基百科上的一点点问题迅速解决了这个问题:这些是distributive法律。欢乐,哦,快乐,Kmett给了我们distribute:
class Functor g => Distributive g where
distribute :: Functor f => f (g a) -> g (f a)
它甚至有一个cotraverse
,因为它是Travsersable
的双重!可爱!!但是,我注意到没有(,)
实例。哦,哦。因为,是的,“默认的c
值”在哪里?然后我意识到,呃,我可能需要一些基于bifunctor的双向分配?也许对bitraversable来说是双重的?概念:
class Bifunctor g => Bidistributive g where
bidistribute :: Bifunctor f => f (g a b) (g a c) -> g a (f b c)
这似乎是我所说的分配法的结构。我在Haskell中找不到这样的东西,因为我实际上在编写C#,这本身对我来说无关紧要。然而,对我来说重要的是不要提出虚假的抽象,并且尽可能地在我的代码中识别出合法的抽象,无论它们是否表达,都是为了我自己的理解。
我目前在我的C#代码中有一个.InsideOut(<default>)
函数(扩展方法)(什么是黑客,对吧!)。我是否完全偏离基础来创建一个(是的,可悲的单形).Bidistribute(...)
函数(扩展方法)来替换它并在调用它之前将左边的“默认”映射到左边的情况(或者只是识别“bidistributive” “从里到外”的性格)?
bidistribute
不能这样实施。考虑一些简单的例子
data Biconst c a b = Biconst c
instance Bifunctor (Biconst c) where
bimap _ _ (Biconst c) = Biconst c
然后我们就有了专业化
bidistribute :: Biconst () (Void, ()) (Void, ()) -> (Void, Biconst () () ())
bidistribute (Biconst ()) = ( ????, Biconst () )
显然没有办法填补空白,需要有Void
类型。
实际上,我认为你真的需要那里的Either
(或者与它同构的东西),而不是任意的bifunctor。那么你的功能就是
uncozipL :: Functor f => Either (f a) (f b) -> f (Either a b)
uncozipL (Left l) = Left <$> l
uncozipL (Right r) = Right <$> l
它被定义为in adjunctions
(找到using Hoogle)。
根据@ leftaroundabout关于查看附录的提示,除了他在uncozipL
中提到的his answer之外,如果我们推迟“默认左右两者中的第一个值”,我们也可以用unzipR
来解决这个问题:
unzipR :: Functor u => u (a, b) -> (u a, u b)
然后,仍然需要映射对中的第一个元素,并用either (const "default") id
之类的东西拉出值。有趣的是,如果你使用uncozipL
,你需要知道其中一个是一对。如果你使用unzipR
,你需要知道一个是其中之一。在这两种情况下,您都不使用抽象的bifunctor。
此外,我正在寻找的模式或抽象似乎是distributive lattice。维基百科说:
如果以下附加标识适用于L中的所有x,y和z,则格(L,∨,∧)是分布的:
x ∧ (y ∨ z) = (x ∧ y) ∨ (x ∧ z).
这正是我在许多不同地方发现的财产。