笛卡尔(Profunctor)的例子?

时间:2018-12-14 10:27:14

标签: haskell

我正在检查以下代码示例,发现一旦实现“ first”并成为Cartisian成员,就很难弄清楚如何使用(->)和(Star f)。

有人可以为他们提供一些易于理解的示例吗?谢谢。

-- 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)

1 个答案:

答案 0 :(得分:6)

注意,提前发表意见!

发音器有点过于抽象。 IMO,我们首先应该谈论的是类别;实际上,大多数发音者都是类别,反之亦然。该profunctor类可能具有有效的用法,但实际上它受到的限制更大,并且与 Hask 类别相关。我更喜欢通过在最后一个参数中使用箭头构造函数为 Hask -功能和在pænultimate参数中使用反变量 Hask -功能性的类别来明确这一点。是的,这是一个令人mouth舌的问题,但这就是重点:这实际上是一个非常具体的情况,而且通常事实证明您确实只需要一个不太具体的类别。
 具体而言,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

这是category-agnostic id。 (您还可以用***secondfirst来定义second f=swap.first f.swapf***g=first f.second g,但这与IMO纠缠不清。)

要了解为什么我更喜欢用这种方式而不是使用profunctor,我想举一个简单的例子,即不是 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.
 }

这不是不是的发音器:尽管您可以使用此特定实现编写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

好的,这看起来很琐碎。为什么这很有趣?线性映射可以有效地存储为矩阵,但从概念上讲,它们主要是函数。因此,有必要像处理功能一样处理它们。在这种情况下,.有效地实现了矩阵乘法,而***则以类型安全的方式将block diagonal matrix组合在一起。

很显然,您还可以使用不受限制的功能来完成所有这些操作,因此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)

Starkleisli category的前身,您可能已经听说过,它是使用单子计算链的一种方法。因此,让我们直接来看一个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操作链接在一起,而没有使用可以窥视/修改所传递的 data 的接口。这对于安全应用程序可能会很有趣。 (我只是编造了这个示例;我确信可以找到较少人为的示例。)

到目前为止,无论如何,到目前为止我还没有真正回答这个问题,因为您不是在询问笛卡尔类别,而是询问strong profunctors。那些确实提供了几乎相同的界面:

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组成。我相信,没有任何组成,我们将无法建立非常复杂的示例。

一个大问题变成:为什么使用profunctor界面,而不是基于类别的界面?在没有组合的情况下必须完成哪些问题?答案几乎取决于Category的{​​{1}}实例:在这里,我不得不提出Star的严格要求。对于profunctor实例,这不是必需的:它们仅需要Monad f。因此,对于Functor f作为强大的发音器的大多数重点示例,您需要查看不是 个应用函数/单子的基本函子。 Van Laarhoven lenses中有一个与此类函子相关的重要应用程序,这些函子的内部实现确实可能为强者提供了最有见地的例子。每当我浏览镜头库的源代码时,我都会一直感到头晕,但我认为Strong Indexed是一个非常有影响力的实例。