微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

(->) 的 Profunctor 实例定义了 dimap 和 lmap/rmap 有什么原因吗?

如何解决(->) 的 Profunctor 实例定义了 dimap 和 lmap/rmap 有什么原因吗?

source code on Hackage 中,我读到:

instance Profunctor (->) where
  dimap ab cd bc = cd . bc . ab
  {-# INLINE dimap #-}
  lmap = flip (.)
  {-# INLINE lmap #-}
  rmap = (.)
  {-# INLINE rmap #-}

但是对于 dimaplmap/rmap/Profunctor认实现需要一个人只定义 lmap 和 {{1} },或rmap;定义所有这些是不必要的。

为什么它们都被定义了?

解决方法

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

但是,使用下面的测试程序,我尝试使用 dimap/rmap/lmap、仅 dimap 和 {{1} 的所有三个定义实例}/rmap ,并且测试函数 lmaplr 的核心在使用 b 编译时在所有三种情况下完全相同:

-O2

虽然对于更复杂的示例,编译器可能无法优化 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 id 的默认定义,但在我看来极不可能,因此手动编码的 {{1} } 和 rmap = dimap id 没有任何区别。

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

更新: 在评论中,@4castle 询问没有优化会发生什么。警告说“因为它改进了 lmap 代码”并没有让我觉得是任何事情的合理论据,我看了看。

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

rmap

虽然明确的 id 定义最终产生了大致相同的 Core,或者可以说更糟。

-- 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 由于额外的 -- 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)) 而优于默认值。

dimap

在另一条评论中,@oisdk 骂我不切实际的测试。我要指出,内联递归失败在这里并不是真正的问题,因为 flip-- 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) dimap 都不是递归的。特别是,简单地以递归方式“使用”其中之一,例如 lmap 不会干扰内联或优化,并且 rmap 的生成代码与显式和默认 {{ 1}}。

此外,将 foo = foldr rmap id/foo 定义中的类/实例拆分为单独的模块对我的测试程序没有任何影响,也不会将其拆分为三个模块:类、实例、和 rmap/l,所以跨模块边界内联在这里似乎不是什么大问题。

对于非专业的多态用法,我想这将归结为生成的 r 字典。我看到以下内容似乎表明具有默认 lr 的显式 Profunctor (->) 产生比替代方案更好的代码。问题似乎是 dimap 在这里没有得到适当优化,因此明确的 lmap 定义适得其反。

rmap

如果有人有一个示例,其中这些显式定义生成更好的 flip (.) 代码,这将是一个很好的替代答案。

这是我的测试程序。我用 lmap 编译。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。