处理“BOX”的 Haskell 函子实现?

smo*_*ing -1 haskell functor category-theory category-abstractions

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

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 的著名文章。

图片中的函子、应用程序和单子

这里,

足够简单。让我们通过说任何值都可以在上下文中来扩展这一点。现在,您可以将上下文视为一个盒子,您可以在其中放置一个值:

在此处输入图片说明

或者

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

在此处输入图片说明

我一直觉得Functor函子范畴论的概念,并从“盒子”,包装及解包到/的概念不匹配良好。

问题点 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。

为什么函子范畴论的概念包装及展开从“盒子” /的概念不匹配呢?

编辑:

即使对于IOfunctor,在组合过程中,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
  
Run Code Online (Sandbox Code Playgroud)

Mar*_*ann 6

范畴论的思想是如此抽象,以至于任何试图提供直观介绍的人都会冒着将概念简化到可能使人们感到困惑的地步的风险。作为该空间文章系列的作者,我可以证明在有人误解文本之前不需要太多不精确的语言。

我不知道具体的文章,但我相信它可能表现出相同的特征。该包装/解包的比喻适合仿函数的主要子集(例如Maybe[]Either l等),但不是全部。

众所周知,您不应该打开包装IO;那是设计使然。在这一点上,包装/展开的比喻分崩离析。面对IO.

确实,概念不匹配。我想说,包装/展开比喻作为介绍可能很有用,但与往常一样,您可以拉伸比喻的程度是有限的。

Functor实例是如何实现的?大多数介绍哈斯克尔会告诉你怎么写fmapMaybe[]和一些其他类型。如果有机会,自己实施它们也是一个很好的练习。

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

同样,这IO是规则的一个很大的例外。据我所知,它的Functor, Applicative,Monad等实例不是在(安全)Haskell 中实现的,而是在构成编译器核心的不安全代码(我相信是 C 或 C++)的小核心中实现的和/或运行时环境。没有(明确的、可见的、安全的)展开IO. 我认为这是更有帮助想IOFunctor为实例结构保留的地图,它是。

关于范畴论和 Haskell 对应关系的更多细节,我推荐Bartosz Milewski 的关于这个主题的系列文章

  • @smooth_writing “Actions”链是什么意思?我不确定我们是否在谈论同一件事......我在“方框插图”的上下文中解释*展开*的方式是,有一个(可能是部分)函数“fa -> a”。`IO` 不存在这样的函数。没有(安全)方法可以从“IO a”中解开“a”,对其应用一些纯函数,然后将其放回“IO”中。不过,AFAICT 就是这个插图所暗示的意思。 (2认同)

fdr*_*ger 5

看图片中的箭头。有没有办法从仿水平恢复到非仿函数级别去。您需要一个从F(x)to 开始的函数x,但是 - 正如您所看到的 - 没有定义。

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

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

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

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

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