【问题标题】:Haskell functor implementation dealing with "BOX"?处理“BOX”的 Haskell 函子实现?
【发布时间】:2020-08-20 23:48:52
【问题描述】:

在范畴论中,Functor的概念如下:

https://ncatlab.org/nlab/show/functor

在 Haskell 中,Functor 类型可以表示为:

fmap :: (a -> b) -> f a -> f b

https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html

我可以看到两者确实很好地对应。

然而,一旦我们真正尝试将这个 Functor 概念实现到代码中,似乎不可能像上图那样简单地定义Ffmap

其实有一篇关于 Functor/Monad 的著名文章。

Functors, Applicatives, And Monads In Pictures

这里,

足够简单。让我们通过说任何值都可以在上下文中来扩展它。现在你可以把上下文想象成一个盒子,你可以在里面放一个值:

以下是我们编写 fmap (+3)(仅 2)时幕后发生的事情:

我一直对Functor 的感觉是范畴论中的 Functor 概念wrap&unwrap to/from "BOX" 的概念不太匹配。 p>

问题点 1。

fmap :: (a -> b) -> f a -> f b

https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html

在 Haskell 中 wrap&unwrap to/from "BOX" 的实际实现在哪里?

问题点 2。

为什么范畴论中的 Functor 概念wrap&unwrap to/from "BOX" 的概念不匹配?

编辑:

即使对于IO 函子,在合成过程中,f 也被解包:

  // f is unwrapped in composition process
  const compose = g => f => x => g(f(x));

  const fmap = compose;

  const print = a => () => console.log(a);
  
  // safely no side-effect
  const todo = fmap(print("bar"))(print("foo"));

  //side effect
  todo(undefined); // foo bar

  // with pipleline operator (ES.next)
  //
  //  const todo = print("foo")
  //    |> fmap(print("bar"))
  //    |> fmap(print("baz"));

  //  todo(undefined); // foo bar baz
  

【问题讨论】:

  • 欢迎来到 Stack Overflow。我们喜欢每个帖子一个问题,它可以帮助未来的学习者找到他们正在寻找的东西。
  • 谢谢,我明白了,但是有些问题可以分为多个点,我认为不应该为以后的学习者单独提出。这是一个问题。
  • @MichaelLitchard 并且在您阅读我对您个人意见的不同意见后,请不要投反对票。我相信我所做的和所说的都没有错。公平地说,只有我的错误是写了问题2,然后让你这样评论。我修改为问题点。
  • @smooth_write 你假设知道谁对你投了反对票以及为什么。这看起来很糟糕。

标签: haskell functor category-theory category-abstractions


【解决方案1】:

类别理论的思想是如此抽象,以至于任何试图提供直观介绍的人都冒着将概念简化到可能使人们感到困惑的风险。作为an article series in this space 的作者,我可以证明在有人误解文本之前不需要太多不精确的语言。

我不知道具体的文章,但我相信它可能表现出相同的特征。 wrap/unwrap 隐喻适用于大量仿函数(例如Maybe[]Either l 等),但不是全部。

众所周知,你是not supposed to unwrap IO;这是设计使然。到那时,包裹/展开比喻就分崩离析了。在IO面前已经失效了。

确实,概念不匹配。我想说的是 wrap/unwrap 隐喻作为介绍可能很有用,但与往常一样,你可以扩展隐喻的程度是有限度的。

Functor 实例是如何实现的?大多数对 Haskell 的介绍将向您展示如何为 Maybe[] 和其他一些类型编写 fmap。如果有机会,自己实施它们也是一个很好的练习。

GHC 及其生态系统是开源的,因此如果您想知道特定实例是如何实现的,您可以随时查看源代码。

同样,IO 是该规则的一个大例外。据我了解,它的FunctorApplicativeMonad 等实例没有在(安全)Haskell 中实现,而是在不安全代码的小核心(我相信 C 或 C++)中实现它构成了编译器和/或运行时环境的核心。 IO 没有(显式、可见、安全)展开。我认为将IOFunctor 实例视为结构保留映射会更有帮助。

更多关于范畴论与Haskell的对应关系,我推荐Bartosz Milewski's article series on the topic

【讨论】:

  • 谢谢!首先,我很高兴您回答了我的问题,因为我已经阅读了您的系列文章以及 Bartosz Milewski 的(他的 YouTube 也是)。
  • 对于IO,我注意到它很难实现,但是,最终,我发现它只是to-do 函数的组合(在Haskell 中,它被称为action) .对于 wrap/unwrap 的比喻,由于函数组合是 f => g => x => x |> f |> gf 在某种意义上在这个过程中被解包,没有实际的副作用。
  • @smooth_writing IO 真的没有展开。 IO a -> a 类型没有 safe 函数。与Maybe[]Either 不同,您不能通过模式匹配来“获取”IO 框中的值。你可以做的是映射一个态射a -> bIO的“世界”中的一个态射:IO a -> IO b
  • @smooth_writing 抱歉,我不懂 JavaScript...看起来您将 fmap 定义为合成。如果是这样,那不是IO;这就是 reader 函子。
  • @smooth_writing Actions 链是什么意思?我不确定我们是否在谈论同一件事......我在“盒子插图”的上下文中解释 unwrapping 的方式是有一个(可能是部分)函数@987654357 @。 IO 不存在这样的功能。没有(安全的)方法可以从IO a 中解开a,对其应用一些纯函数,然后将其放回IO。不过,AFAICT 就是插图所暗示的。
【解决方案2】:

看图中的箭头。 没有办法从函子层回到非函子层。您需要一个从 F(x)x 的函数,但是 - 如您所见 - 没有定义。

有一些特定的仿函数(如Maybe)提供“解包”功能,但此类功能始终是一个附加组件,它在仿函数之上提供。例如,你可能会说:Maybe 是一个函子,而且它还有一个有趣的属性:有一个偏函数将Maybe X 映射到 X,并反转 pure

更新(出现附加问题后) 盒子和仿函数的概念根本不匹配。此外,据我所知,还没有找到关于函子(或单子或应用程序)的好比喻——而不是因为缺乏尝试。这并不奇怪:大多数抽象都缺乏好的隐喻,正是因为抽象和隐喻(在某种程度上)是截然相反的。

抽象将概念剥离到其核心,只留下最基本的要素。另一方面,隐喻扩展了一个概念,拓宽了语义空间,暗示了更多的意义。当我说:“你的眼睛有巧克力的颜色”时,我是在抽象“颜色”的概念。但我也隐喻地将眼睛和巧克力联系起来:我认为它们的共同点不仅仅是颜色:丝滑的质地、甜味、愉悦——所有这些概念都存在,尽管没有一个被命名。如果我说“你的眼睛有排泄物的颜色”,所使用的抽象概念将完全相同——但隐喻意义:非常不同。我什至不会对逻辑学家这么说,即使逻辑学家在技术上会理解这句话没有冒犯性。

当处于自动驾驶状态时,大多数人会用隐喻而不是抽象来思考。用前者解释后者时必须小心,因为意义会溢出。当你听到“一个盒子”时,你脑海中的自动驾驶仪会告诉你,你可以把东西放进去,然后把东西拿出来。函子不是那样的。所以这个比喻是有误导性的。

Functor 体现了... 盒子或包装器的抽象,但它允许我们在不打开包装的情况下处理它们的内容。这种缺乏展开正是使函子变得有趣的原因:否则fmap 将只是展开、应用函数和包装结果的语法糖。研究函子让我们了解在不展开值的情况下有多少是可能的——而且,更好、更有启发性的是,它让我们了解在不展开的情况下什么是不可能的。导致应用程序、箭头和 monad 的步骤向我们展示了如何通过允许额外的操作来克服一些限制,但是 stukk 不允许展开,因为如果我们允许展开,这些步骤将毫无意义(即变得微不足道)。

【讨论】:

  • 谢谢,事实上,是的,我已经注意到了,我应该提到你回答的第一部分。虽然,这并不是我从根本上要求的,作为我的问题的补充,+1。
  • @smooth_writing:有一点时间,写了一篇文章 :-) TLDR:没有展开。关键是没有展开。因此,盒子作为函子隐喻非常糟糕。与所有隐喻一样,应用于摘要时。
  • 谢谢,但正如您之前提到的,没有从F(x)x 的态射,是不是不可能实现Functor?目前,我认为函子图只是实现的一个类型结果,或多或少需要来回走,对应的是Box unwrap&wrap with Lambda calculus
  • @smooth_writing 不!一遍又一遍地重复的重点是仿函数没有F(x) -> x。该图是正确的——它不是“解释”、“实现”或“副作用”。函子并不意味着“展开”。离开函子的值的概念是没有意义的。它不是一个盒子——它是一个黑洞。使事物消失的魔术师的帽子。一张通往另一个维度的单程票。现在,如果某个东西既是函子又是糖果,你可以打开它吃掉它。但那是因为它是糖果,而不是函子。
  • @smooth_writing 获取人员列表。你如何将它们“解开”给一个人?看?这里没有对称性。将单个人包装到列表中是明确定义的,而展开则不是。将x 包装成Maybe 是明确定义的,但无法编写通用的展开,因为无法为Nothing 获取x 实例。这个有趣的哲学事实,包装和展开之间缺乏对称性 - 正是使函子成为函子的原因。所以与其反抗,不如拥抱它……
猜你喜欢
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多