单子 "unboxing"

Monad "unboxing"

我的问题是在学习教程 Functors, Applicatives, And Monads In Pictures and its JavaScript version 时提出的。

当文字说仿函数从上下文中解包值时,我理解 Just 5 -> 5 转换正在发生。根据 What does the "Just" syntax mean in Haskell? ,只是 Maybe monad 的 "defined in scope"。

我的问题是整个展开的东西有什么神奇之处?我的意思是,有一些自动解包 "scoped" 变量的语言规则有什么问题?在我看来,此操作只是某种 table 中的查找,其中符号 Just 5 对应于整数 5.

我的问题受到JavaScript版本的启发,其中Just 5是原型数组实例。所以展开确实不是火箭科学。

这是 "for-computation" 类型的原因还是 "for-programmer" 类型的原因?为什么我们在编程语言层面上区分Just 55

"unwrap" 的含义取决于容器。 Maybe 只是一个例子。当容器是 [] 而不是 Maybe.

时,"Unwrapping" 意味着完全不同的东西

整个解包的神奇之处在于抽象:在 Monad 中,我们有一个 "unwrapping" 的概念,它抽象了容器的性质;然后它开始变得 "magical"...

你问 Just 是什么意思:Just 只不过是 Haskell 中的数据类型构造函数,通过数据声明定义,如 :

 data Maybe a = Just a | Nothing

Just 获取类型 a 的值并创建类型 Maybe a 的值。这是 Haskell 区分类型 a 的值和类型 Maybe a

的方法

首先,我认为如果不了解类似 Haskell 的类型系统(即不学习像 Haskell 这样的语言),您就无法理解 Monad 等。是的,有很多教程声称不是这样,但是我在学习之前已经阅读了很多教程 Haskell 并且我没有理解。所以我的建议是:如果你想了解 Monad,至少要学习一些 Haskell.

关于你的问题"Why do we distinguish Just 5 from 5 on the programming language level?"。为了类型安全。在大多数恰好不是 Haskell nullnilwhatever 的语言中,通常用来表示没有值。然而,这通常会导致 NullPointerExceptions 之类的结果,因为您没有预料到可能不存在值。

在Haskell中没有null。因此,如果您有一个 Int 类型的值,或者其他任何类型的值,则该值不能是 null。你保证有一个值。伟大的!但有时你实际上 want/need 来编码值的缺失。在 Haskell 中,我们为此使用 Maybe。所以 Maybe Int 类型的东西可以是 Just 5Nothing 之类的东西。这样很明显,该值可能不存在,并且您不会不小心忘记它可能是 Nothing 因为您必须显式解包该值。

这与 Monad 没有任何关系,除了 Maybe 恰好实现了 Monad 类型 class(类型 class 有点像 Java界面,如果您熟悉 Java)。那就是 Maybe 主要不是 Monad,但恰好也是 Monad。

一个可能的例子是:考虑 Haskell 类型 Maybe (Maybe Int)。它的值可以是下面的形式

  • Nothing
  • Just Nothing
  • Just (Just n) 对于某个整数 n

没有 Just 包装器,我们无法区分前两个。

的确,可选类型 Maybe a 的全部意义在于向现有类型 a 添加一个新值 (Nothing)。为了确保 Nothing 确实是一个新值,我们将其他值包装在 Just.

它在类型推断期间也有帮助。当我们看到函数调用 f 'a' 时,我们可以看到 f 是在类型 Char 上调用的,而不是在类型 Maybe CharMaybe (Maybe Char) 上调用的。类型类系统将允许 f 在每种情况下都有不同的实现(这类似于某些 OOP 语言中的 "overloading")。

我认为你从错误的方向来看这个问题。 Monad 明确地 不是 关于展开。 Monad 关于构图。

它允许您将类型 a -> m b 的函数与类型 m a 的值组合(不一定应用)以获得类型 m b 的值。我能理解您可能认为这样做的明显方法是将 m a 类型的值解包为 a 类型的值。但是很少有 Monad 个实例是这样工作的。事实上,唯一可以那样工作的是那些等同于 Identity 类型的。对于 Monad 的几乎所有实例,都无法展开值。

考虑 Maybe。当起始值为 Nothing 时,将 Maybe a 类型的值展开为 a 类型的值是不可能的。 Monadic 组合必须做一些比展开更有趣的事情。

考虑 []。将 [a] 类型的值展开为 a 类型的值是不可能的,除非输入恰好是长度为 1 的列表。在所有其他情况下,单子组合所做的事情比展开更有趣。

考虑 IOgetLine :: IO String 之类的值不包含 String 值。显然不可能打开包装,因为它没有包装任何东西。 IO 值的单子组合不会解包任何东西。它将 IO 个值组合成更复杂的 IO 个值。

我认为值得调整您对 Monad 含义的看法。如果它只是一个解包接口,那将毫无用处。不过,它更微妙。这是一个组合界面。

首先,您需要从问题中删除 monad。他们与此无关。将这篇文章作为对 monad 的一种观点,也许它不适合你,你可能在类型系统中仍然一知半解 haskell.

因此,您的问题可以改写为:为什么没有隐式转换 Just 5 => 5?但答案很简单。因为值 Just 5 的类型是 Maybe Integer,所以这个值可能是 Nothing,但在这种情况下编译器必须做什么?只有程序员才能解决这种情况。

但是还有一个更不舒服的问题。有类型,例如 newtype Identity a = Identity a。它只是一些价值的包装。那么,为什么没有隐式转换Identity a => a?

简单的答案是 - 试图实现这一点会导致不同的系统类型,它不会有当前存在的许多优良品质。据此,可以为了其他可能性的利益而牺牲它。

My question is, what is so magical about the whole unwrapping thing?

这没什么神奇的。您可以使用普通的模式匹配(此处为 case 表达式的形式)来定义...

mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe f mx = case mx of
    Just x -> Just (f x)
    _ -> mx

... 这与 Maybefmap 完全相同。 Functor class 添加的唯一东西——毫无疑问,这是一个非常有用的东西——是一个额外的抽象级别,涵盖了可以映射的各种结构。

Why do we distinguish Just 5 from 5 on programming language level?

Just 55 之间的区别更有意义的是它们 类型 之间的区别——例如在 Maybe IntInt 之间。如果您有 x :: Int,您可以确定 x 是一个您可以使用的 Int 值。但是,如果你有 mx :: Maybe Int,你就没有这样的确定性,因为 Int 可能会丢失(即 mx 可能是 Nothing),并且类型系统会强制你承认并处理这种可能性。

另请参阅: for further comments on the usefulness of Maybe (which isn't necessarily tied to classes such as Functor and Monad); ,了解有关 class 的有用性的更多评论,例如 FunctorMonad(除了 Maybe 示例)。