【问题标题】:Might laziness also mean a value of function depends on context where it is called?懒惰是否也意味着函数的值取决于调用它的上下文?
【发布时间】:2015-07-15 04:51:05
【问题描述】:

我正在尝试学习 Haskell,在使用应用函子时,我发现一件事让我很困惑。

让我们定义以下函数g,它返回一些函子:

*Main> let g = pure (2*)
*Main> :t g
g :: (Num a, Applicative f) => f (a -> a)

由于返回类型是 some 函子,我可以在这两个函数中使用 g 作为参数:

f1 :: (Num a) => [a -> a] -> a
f1 (g:[]) = g 3

f2 :: (Num a) => Maybe (a -> a) -> a
f2 (Just g) = g 4

但这意味着函数 g 返回的值也取决于将被评估的上下文! (可能是 List 和 Maybe。)这也是懒惰的属性吗?因为到目前为止,我一直在以一种在需要时计算值的方式来考虑惰性,但它在定义时已经确定(对于 g in let 表达式)。

【问题讨论】:

  • 这与懒惰无关。这只是常规的重载。
  • @augustss 好的,现在我知道在 Haskell 中也可以重载单个值(因此我的示例过于复杂;)。但我仍然认为这在某种程度上取决于懒惰。如果在需要它的值时不评估该值,就不可能重载它,对吧?
  • @MartinJiřička 具有多态类型的值实际上并不是惰性行为——它们的行为类似于函数。也就是说,您每次使用它们时都会对它们进行评估,而不仅仅是第一次使用它们。所以它不起作用是因为懒惰(它不适用于懒惰),而是因为与单态值相比,多态值得到特殊处理。
  • 我对自己的回应是:不,这与懒惰无关,因为它与何时计算值无关,而是选择了哪个值(根据预期的类型)。但现在我发现我的原始示例中的函数与本例略有不同。
  • 另外,这个想法是 g 确实是某种闭包(当我将它变成一个从 Applicative ff (a -> a) 的函数时,它是明确的),因此,它是已经一个值。微妙之处在于,它的参数是由编译器而不是用户在其使用站点选择的。

标签: haskell applicative


【解决方案1】:

正如@augustss 所说,这与懒惰无关,而是您正在使用类型类这一事实。为了使这一点更清楚,您可以通过显式传递包含类定义的所有函数的记录来对类型类进行建模。如果您想查找有关它的更多信息,这种技术称为字典传递

我们从一些扩展开始。

{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE RecordWildCards #-}

然后给记录类型打包Applicative 应该具有的功能(实际上,您还会有一个字段说fFunctor,但为简洁起见,我在此省略)。

data Applicative f =
  Applicative { pure :: forall a. a -> f a
              , app  :: forall a b. f (a -> b) -> f a -> f b
              }

我们可以将您的函数 g 定义为记录说 fApplicative 并提供您所描述的行为(我将 Num 保留为类约束,但类似地,它可能是翻译为记录传递)。

g :: Num a => Applicative f -> f (a -> a)
g Applicative{..} = pure (2*)

您的两个函数 f1f2 仍然是有效定义:

f1 :: Num a => [a -> a] -> a
f1 (g:[]) = g 3

f2 :: Num a => Maybe (a -> a) -> a
f2 (Just g) = g 4

现在,我们想将它们应用到 g,但有一个问题:g 的函数类型期望传递 Applicative f 记录。好吧,我们可以定义Applicative[]Maybe 实例:

applicativeList :: Applicative []
applicativeList =
  Applicative { pure = (:[])
              , app  = \ fs as -> fs >>= \ f -> fmap f as
              }

applicativeMaybe :: Applicative Maybe
applicativeMaybe =
  Applicative { pure = Just
              , app  = \ fs as -> fs >>= \ f -> fmap f as
              }

然后我们必须为应用程序选择正确的类型进行类型检查([] 用于f1Maybe 用于f2):

f1g = f1 (g applicativeList)
f2g = f2 (g applicativeMaybe)

【讨论】:

  • 哇,谢谢你的回答!不幸的是,这对我来说太复杂了。我是否理解正确,而不是函数 pure 的“不可见”重载,而是可以通过参数将方法的“正确”版本显式传递给 g,所以它是可见 g 的返回值取决于返回值的预期类型?
  • 就是这个想法:不用重载,您可以使用我们所说的“字典传递”(包含这些重载运算符定义的记录)。
  • 请注意,您只需要Rank2Types 而不是RankNTypes(至少在您展示的情况下)。
  • @gallais 我会将您的答案标记为已接受。如果您可以使用有关“字典”的信息来扩展您的答案,那就太好了。我发现它非常重要,我认为它应该在答案中,而不仅仅是在评论中。
  • 你说的很对。我在介绍性段落中添加了一个句子来命名此处使用的技术。
猜你喜欢
  • 1970-01-01
  • 2019-09-17
  • 2015-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多