首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么(->)的Profunctor实例同时定义了dimap和lmap/rmap?

为什么(->)的Profunctor实例同时定义了dimap和lmap/rmap?
EN

Stack Overflow用户
提问于 2021-01-24 23:33:34
回答 1查看 79关注 0票数 1

source code on Hackage中,我读到了以下内容:

代码语言:javascript
复制
instance Profunctor (->) where
  dimap ab cd bc = cd . bc . ab
  {-# INLINE dimap #-}
  lmap = flip (.)
  {-# INLINE lmap #-}
  rmap = (.)
  {-# INLINE rmap #-}

但是Profunctor的默认dimap/lmap/rmap实现需要同时定义lmaprmap,或者定义dimap;没有必要定义所有它们。

为什么它们都被定义了,有什么原因吗?

EN

回答 1

Stack Overflow用户

发布于 2021-01-25 03:46:55

正如@FyodorSoikin评论的那样,其意图可能是lmaprmap手工编码的定义将比基于dimap的默认定义更有效。

但是,使用下面的测试程序,我尝试仅使用dimap/rmap/lmapdimaprmap/lmap的全部三个来定义实例,并且使用-O2编译时,测试函数lrb的核心在所有三种情况下都是完全相同的

代码语言:javascript
复制
b = \ x -> case x of { I# x1 -> I# (+# 15# (*# 6# x1)) }
r = \ x -> case x of { I# x1 -> I# (+# 15# (*# 3# x1)) }
l = \ x -> case x of { I# x1 -> I# (+# (*# x1 2#) 5#) }

虽然对于更复杂的示例,编译器可能无法优化lmap f = dimap f idrmap = dimap id的默认定义,但我认为这是非常不可能的,因此手动编码的lmaprmap没有任何区别。

我认为解释是,即使是非常熟练的Haskell程序员,如Edward Kmett,仍然低估了编译器,并对他们的代码进行了不必要的手工优化。

更新:在一条评论中,@4castle询问了没有优化会发生什么。“因为它改进了-O0代码”对我来说并不是一个合理的论据,我看了一下。

在未优化的代码中,显式的rmap定义通过避免与id的额外组合来生成更好的核心

代码语言:javascript
复制
-- explicit `rmap`
r = . (let { ds = I# 3# } in \ ds1 -> * $fNumInt ds1 ds)
      (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds)

-- default `rmap`
r = . (let { ds = I# 3# } in \ ds1 -> * $fNumInt ds1 ds)
  (. (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds) id)

虽然显式的lmap定义最终会产生核心,但这几乎是相同的,或者更糟。

代码语言:javascript
复制
-- explicit `lmap`
$clmap = \ @ a @ b1 @ c -> flip .
l = $clmap
      (let { ds = I# 2# } in \ ds1 -> * $fNumInt ds1 ds)
      (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds)

-- default `lmap`
l = . id
      (. (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds)
         (let { ds = I# 2# } in \ ds1 -> * $fNumInt ds1 ds))

作为上述定义的结果,由于额外的flip,显式dimap比缺省值更好。

代码语言:javascript
复制
-- explicit `dimap`
b = . (let { ds = I# 3# } in \ ds1 -> * $fNumInt ds1 ds)
      (. (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds)
         (let { ds = I# 2# } in \ ds1 -> * $fNumInt ds1 ds))

-- default `dimap`
$clmap = \ @ a @ b1 @ c -> flip .
b = . ($clmap (let { ds = I# 2# } in \ ds1 -> * $fNumInt ds1 ds))
      (. (let { ds = I# 3# } in \ ds1 -> * $fNumInt ds1 ds))
      (let { ds = I# 5# } in \ ds1 -> + $fNumInt ds1 ds)

在另一条评论中,@oisdk责备了我不切实际的测试。我要指出的是,内联递归失败在这里并不是真正的问题,因为dimaplmaprmap都不是递归的。特别是,简单地以递归方式“使用”其中之一,如foo = foldr rmap id,不会干扰内联或优化,并且为foo生成的代码与显式和缺省的rmap相同。

此外,将l/r定义中的类/实例划分为单独的模块对我的测试程序也没有影响,也不会将其划分为三个模块:类、实例和l/r,因此跨模块边界内联看起来不是什么大问题。

对于非专门化的多态用法,我猜它将归结为生成的Profunctor (->)字典。我看到以下内容似乎表明,具有缺省lmaprmap的显式dimap比其他方法生成的代码更好。问题似乎是flip (.)在这里没有得到适当的优化,所以显式的lmap定义适得其反。

代码语言:javascript
复制
-- explicit `dimap`, `rmap`, and `lmap`
$fProfunctor->
  = C:Profunctor $fProfunctor->_$cdimap $fProfunctor->_$clmap .
$fProfunctor->_$cdimap
  = \ @ a @ b @ c @ d ab cd bc x -> cd (bc (ab x))
$fProfunctor->_$clmap = \ @ a @ b @ c x y -> . y x

-- explicit `lmap`, `rmap`, default `dimap`
$fProfunctor->
  = C:Profunctor $fProfunctor->_$cdimap $fProfunctor->_$clmap .
$fProfunctor->_$cdimap
  = \ @ a @ b @ c @ d eta eta1 x eta2 -> eta1 (x (eta eta2))
$fProfunctor->_$clmap = \ @ a @ b @ c x y -> . y x

-- explicit `dimap`, default `lmap`, `rmap`
$fProfunctor->
  = C:Profunctor
      $fProfunctor->_$cdimap $fProfunctor->_$clmap $fProfunctor->1
$fProfunctor->_$cdimap
  = \ @ a @ b @ c @ d ab cd bc x -> cd (bc (ab x))
$fProfunctor->_$clmap = \ @ a @ b @ c eta bc x -> bc (eta x)
$fProfunctor->1 = \ @ b @ c @ a cd bc x -> cd (bc x)

如果有人有一个例子,这些显式定义可以生成更好的-O2代码,这将是一个很好的替代答案。

这是我的测试程序。我是用ghc -O2 Profunctor.hs -fforce-recomp -ddump-simpl -dsuppress-all -dsuppress-uniques编译的。

代码语言:javascript
复制
module Profunctor where

class Profunctor p where
  dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
  dimap f g = lmap f . rmap g
  {-# INLINE dimap #-}

  lmap :: (a -> b) -> p b c -> p a c
  lmap f = dimap f id
  {-# INLINE lmap #-}

  rmap :: (b -> c) -> p a b -> p a c
  rmap = dimap id
  {-# INLINE rmap #-}

instance Profunctor (->) where
  -- same core if dimap is commented out or if lmap/rmap are commented out
  dimap ab cd bc = cd . bc . ab
  lmap = flip (.)
  rmap = (.)

l :: Int -> Int
l = lmap (*2) (+5)

r :: Int -> Int
r = rmap (*3) (+5)

b :: Int -> Int
b = dimap (*2) (*3) (+5)
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65872479

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档