惰性是否也意味着函数的值取决于调用它的上下文?
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 值,但在定义时已经确定(对于 g 在 let 表达式中)。
正如@augustss 所说,这与懒惰无关,而是你正在使用类型class。为了使这一点更清楚,您可以通过显式传递包含 class 定义的所有函数的记录来对 typeclasses 进行建模。这种技术称为字典传递,以防您想查找更多相关信息。
我们从一些扩展开始。
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
然后给记录类型包装 Applicative
应该具有的功能(实际上你也有一个字段说 f
是 Functor
但我为简洁起见,此处省略)。
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*)
您的两个函数 f1
和 f2
仍然是有效的定义:
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
}
然后我们必须选择正确的应用程序进行类型检查([]
用于 f1
和 Maybe
用于 f2
):
f1g = f1 (g applicativeList)
f2g = f2 (g applicativeMaybe)
我正在努力学习 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 值,但在定义时已经确定(对于 g 在 let 表达式中)。
正如@augustss 所说,这与懒惰无关,而是你正在使用类型class。为了使这一点更清楚,您可以通过显式传递包含 class 定义的所有函数的记录来对 typeclasses 进行建模。这种技术称为字典传递,以防您想查找更多相关信息。
我们从一些扩展开始。
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
然后给记录类型包装 Applicative
应该具有的功能(实际上你也有一个字段说 f
是 Functor
但我为简洁起见,此处省略)。
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*)
您的两个函数 f1
和 f2
仍然是有效的定义:
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
}
然后我们必须选择正确的应用程序进行类型检查([]
用于 f1
和 Maybe
用于 f2
):
f1g = f1 (g applicativeList)
f2g = f2 (g applicativeMaybe)