惰性是否也意味着函数的值取决于调用它的上下文?

Might laziness also mean a value of function depends on context where it is called?

我正在努力学习 Haskell,在玩应用函子时,我发现一件事让我感到困惑。

让我们定义以下函数 g returns 一些函子:

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

因为 return 类型是 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 returns 的值还取决于将在其中计算的上下文! (可能既是List又是Maybe。)这也是属性的懒惰吗?因为直到现在我一直在思考惰性的方式,即在需要时 calculated 值,但在定义时已经确定(对于 glet 表达式中)。

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

我们从一些扩展开始。

{-# 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 定义为记录表明 f 是一个 Applicative 并提供您所描述的行为(我将 Num 保留为class 约束,但类似地,它可以转换为记录传递)。

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)