笛卡尔(Profunctor)的例子?

Don*_*ein 3 haskell category-abstractions profunctor

我正在通过以下代码示例,发现很难弄清楚如何使用( - >)和(Star f)一旦他们实现'first'并成为Cartisian的成员.

有人可以提供一些容易理解的例子吗?谢谢.

-- 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)
Run Code Online (Sandbox Code Playgroud)

lef*_*out 6

注意,提前意见!

Profunctors是一个过度嗡嗡的抽象.国际海事组织我们应该首先谈论类别 ; 在实践中,大多数产品是类别,但反之亦然.profunctor类可能具有有效用途,但它实际上更加有限并且与Hask类别相关联.我更喜欢通过讨论其箭头构造函数在最后一个参数中是Hask -functors的类别和在pænultimate参数中的逆变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')
Run Code Online (Sandbox Code Playgroud)

哪个允许

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
Run Code Online (Sandbox Code Playgroud)

这是与类别无关的id.(您也可以定义***second来讲first,与second f=swap.first f.swapf***g=first f.second g,但是这IMO粗暴纠缠.)

为了理解为什么我更喜欢这种方式,而不是使用profunctors,我想给出一个不是 profunctor 的简单示例:线性映射.

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.
 }
Run Code Online (Sandbox Code Playgroud)

不是一个profunctor:尽管你可以通过这个特定的实现来写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
Run Code Online (Sandbox Code Playgroud)

好吧,这看起来很微不足道.为什么这很有趣?嗯,线性映射可以有效地存储为矩阵,但从概念上讲,它们最重要的是功能.因此,与功能类似地处理它们是有意义的; 在这种情况下,.有效地实现矩阵乘法***并将块对角矩阵组合在一起,所有这些都是以类型安全的方式.

显然,你可以使用不受限制的功能来完成所有这些,所以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)
Run Code Online (Sandbox Code Playgroud)

Starkleisli类别的前身,你可能已经听说过这是使用monadic计算链接的一种方法.让我们来看一个IO例子:

readFile' :: Star IO FilePath String
readFile' = Star readFile

writeFile' :: Star IO (FilePath,String) ()
writeFile' = Star $ uncurry writeFile
Run Code Online (Sandbox Code Playgroud)

现在我可以做点什么了

copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second readFile'
Run Code Online (Sandbox Code Playgroud)

我为什么要这样做?关键是我已经将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)
Run Code Online (Sandbox Code Playgroud)

因此我可以做出微小的改变

copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second' readFile'
Run Code Online (Sandbox Code Playgroud)

保留基本相同的例子,Strong而不是Cartesian.我仍在使用这个Category作品.而且我相信如果没有任何构图,我们将无法构建非常复杂的例子.

最大的问题是:为什么使用profunctor接口而不是基于类别的接口?没有作文必须要做的问题是什么?答案几乎就是在这样的Category例子中Star:我必须满足这一要求Monad f.这对于profunctor实例来说不是必需的:这些只需要Functor f.因此,对于Star作为强大的profunctor的大多数重点示例,您将需要查看不是 applicative/monads的基本仿函数.这些仿函数相关的一个重要应用是Van Laarhoven 镜头,而那些内部实现确实可能是强有力的探测器最有见地的例子.每当我浏览镜头库的源头时,我都会头晕目眩,但我认为一个影响很大的实例是强索引.