我正在研究下面的代码示例,发现一旦它们实现了“first”并成为Cartisian的成员,就很难弄清楚如何使用(->)和(Star )。
有人能提供一些容易理解的例子吗?谢谢。
-- Intuitively a profunctor is cartesian if it can pass around additional
-- context in the form of a pair.
class Profunctor p => Cartesian p where
first :: p a b -> p (a, c) (b, c)
first = dimap swapP swapP . second
second :: p a b -> p (c, a) (c, b)
second = dimap swapP swapP . first
instance Cartesian (->) where
first :: (a -> b) -> (a, c) -> (b, c)
first f = f `cross` id
instance Functor f => Cartesian (Star f) where
first :: Star f a b -> Star f (a, c) (b, c)
first (Star f) = Star $ (\(fx, y) -> (, y) <$> fx) . (f `cross` id)发布于 2018-12-14 12:19:34
注意,前面有意见!
Pro函子是一个过度流行的抽象。我们应该先讨论范畴;实际上,大多数的程序都是范畴,反之亦然。profunctor类可能有有效的用途,但实际上它比Hask类别有更多的限制。我更喜欢通过讨论箭头构造函数在最后一个参数中是Hask-functors和p n终极参数中的反变体Hask-functors的类别来说明这一点。是的,这是一大口,但这就是重点:这实际上是一个非常具体的情况,而且通常情况下,你真的只需要一个不那么具体的类别。
具体来说,Cartesian更自然地被认为是一类范畴,而不是程序类型:
class Category k => Cartesian k where
swap :: k (a,b) (b,a)
(***) :: k a b -> k a' b' -> k (a,a') (b,b')这允许
first :: Cartesian k => k a b -> k (a,c) (b,c)
first f = f *** id
second :: Cartesian k => k a b -> k (c,a) (c,b)
second f = id *** f这就是id。(你也可以用***和second来定义first,用second f=swap.first f.swap和f***g=first f.second g来定义,但这是不恰当的纠缠。)
为了了解为什么我更喜欢这样做,而不是用终止函数,我想给出一个简单的例子,它不是一个函数:线性映射。
newtype LinearMap v w = LinearMap {
runLinearMap :: v->w -- must be linear, i.e. if v and w are finite-dimensional
-- vector spaces, the function can be written as matrix application.
}这是而不是,虽然您可以使用这个特定的实现编写dimag f g (LinearMap a) = LinearMap $ dimap f g a,但这不会保持线性。然而,这是一个笛卡尔的类别:
instance Category LinearMap where
id = LinearMap id
LinearMap f . LinearMap g = LinearMap $ f . g
instance Cartesian LinearMap where
swap = LinearMap swap
LinearMap f *** LinearMap g = LinearMap $ f *** g好吧,这看起来很琐碎。为什么这很有趣?嗯,线性映射可以有效地存储为矩阵,但从概念上讲,它们是最重要的函数。因此,处理它们类似于函数是有意义的;在这种情况下,.有效地实现了矩阵乘法,而***以一种类型安全的方式组合了一个块对角矩阵。
显然,您也可以使用不受限制的函数来完成所有这一切,因此instance Cartesian (->)实际上是微不足道的。但是,我给出了一个线性映射示例,以激励Cartesian类能够完成一些没有它就没有必要的琐碎工作。
Star是它变得非常有趣的地方。
newtype Star f d c = Star{runStar :: d->f c}
instance Monad f => Category (Star f) where
id = Star pure
Star f . Star g = Star $ \x -> f =<< g x
instance Monad f => Cartesian (Star f) where
swap = Star $ pure . swap
Star f *** Star g = Star $ \(a,b) -> liftA2 (,) (f a) (g b)Star是kleisli范畴的前身,正如您可能听说的那样,这是使用一元计算链的一种方法。让我们直接来看一个IO示例:
readFile' :: Star IO FilePath String
readFile' = Star readFile
writeFile' :: Star IO (FilePath,String) ()
writeFile' = Star $ uncurry writeFile现在我可以做这样的事
copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second readFile'我为什么要这样做?关键是,我将IO操作链接在一起,而没有使用接口来查看/修改传递的数据。对于安全应用程序来说,这可能很有趣。(我只是编了这个例子,我相信可以找到较少人为的例子。)
无论如何,到目前为止,我还没有真正回答这个问题,因为你问的不是笛卡尔类,而是强截子。不过,它们确实提供了几乎相同的界面:
class Profunctor p => Strong p where
first' :: p a b -> p (a, c) (b, c)
second' :: p a b -> p (c, a) (c, b)所以我也可以做些小小的改变
copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second' readFile'在本质上保留相同的示例,但使用Strong而不是Cartesian。不过,我仍然在使用Category组合。我相信,如果没有任何构图,我们就无法建立非常复杂的例子。
大问题是:为什么要使用接口而不是基于类别的接口呢?没有作文就必须做什么问题?答案在很大程度上取决于Category实例Star:在那里,我必须对Monad f提出很高的要求。对于终止程序实例来说,这不是必需的:这些实例只需要Functor f。因此,对于大多数聚焦于Star作为一个强质子函数的例子,您将希望看到不是应用程序/单子的基函子。这类函子相关的一个重要应用是在Van 镜片中,而这些函子的内部实现可能确实给出了强凸函数的最有洞察力的例子。每当我穿过镜头库的源时,我就会头晕,但我认为有一个非常有影响的例子是强索引。
https://stackoverflow.com/questions/53777851
复制相似问题