Haskell 为什么 Applicatives 需要在同一个 Context 中获取态射和数据?
In Haskell why Applicatives need to take morphisms and data in same Context?
我是 Haskell 的新手。这可能是个愚蠢的问题。
因为 Applicative 类型类具有 apply 函数,该函数在同一上下文中获取函数和数据。为什么它不能与众不同并且更通用。
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
为什么我们不能写这样的东西
class Functor f => Applicative f where
(<*>) :: Functor g => g (a -> b) -> f a -> g (f b)
(<*>) gab fa = fmap (\g -> fmap g fa) gab
(<<*>>) :: Functor g => (g (f a) -> f a) -> g (a -> b) -> f a -> f b
(<<*>>) peelOuter gab fa = peelOuter $ gab <*> fa
(>>*<<) :: Functor g => (g (f a) -> g a) -> g (a -> b) -> f a -> g b
(>>*<<) cleanInner gab fa = cleanInner $ gab <*> fa
可以如下使用
-- Extract List from maybe
elfm :: Maybe [a] -> [a]
elfm Nothing = []
elfm (Just xs) = xs
-- Fuse List elements in Maybe []
flem :: Monoid a => Maybe [a] -> Maybe a
flem Nothing = mempty
flem (Just xs) = Just $ foldl (<>) mempty xs
Just (*2) <*> [1,2,3,4]
-- Just [2,4,6,8]
(<<*>>) elfm (Just (*2)) [1,2,3,4]
-- [2,4,6,8]
(>>*<<) flem (Just (++ "Haskell")) ["Hello, "]
-- Just "Hello, Haskell"
而且我读到 Applicatives 的全部意义在于 Functors 提升多参数函数的缺点。这样对吗?
而且我觉得功能应用不尽如人意
add :: Num a => a -> a -> a
add a b = a + b
-- I want to apply [1,2,3] as First arguments and [4,5,6] as 2nd arguments.
-- Like add 1 4, add 2 4, add 3 6
-- But it is give all possibilities of combinations like a tree
-- <*>
-- (+1) (+2) (+3)
-- (1+4)(1+5)(1+6) (2+4)(2+5)(2+6) (3+4)(3+5)(3+6)
并且还将它们与批处理进行了比较,但没有给出非常真实的例子。请为此提供示例。
Applicative
的每个实例都必须有自己的 <*>
实现。这就是为什么我们首先输入 classes 的原因。您的代码具有 class 本身定义的所有方法,没有留下任何实例。这意味着根本没有太多类型 class 。只有一堆通用函数。所有的肉都委托给('<<*>>)
和(>>*<<)
的参数peelOuter
和cleanInner
。让我们更仔细地看看它们。它们或多或少是对称的,所以 (<<*>>)
应该足够了。
(<<*>>) :: Functor g => (g (f a) -> f a) -> g (a -> b) -> f a -> f b
(<<*>>) peelOuter gab fa = peelOuter $ gab <*> fa
其实peelOuter
本来应该是class类型的方法,但是问题不止一个。
第一个问题涉及到两个仿函数,peelOuter
每个对仿函数需要单独实现。也就是说,我们这里有一个双参数类型 class ApplicativePair
,我们需要为每一对创建一个单独的实例。
第二个问题是peelOuter
不能对每一对真正的Applicative
仿函数都实现。不能从 Maybe (Id a)
中提取 Id a
,或者从 IO [a]
中提取 [a]
,或者...
更糟糕的是,当 f
和 g
是同一个函子时,它是否总是可实现的还不清楚。显然,当 f
是一个 monad 时,它就只是一个 join
。然而,并非所有的应用程序都是 monad,而 join
恰恰是应用程序缺乏成为 monad 的地方。所以 peelOuter
,即使这样的类型是可实现的,也会违反一些 monad 法则。那是一件坏事?不一定,如果它仍然遵循适用的法律。但是,您没有提供任何法律,只提供了一堆功能。
任何两个仿函数都是 Functor 和 Applicative 仿函数。这用 newtype Compose
编码,参见 Data.Functor.Compose
。
所以,你的例子可以通过newtype Compose
来解决。
-- Just (*2) <*> [1,2,3,4]
getCompose $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or
getCompose $ (*2) <$> Compose (Just [1,2,3,4])
-- Just [2,4,6,8]
-- (<<*>>) elfm (Just (*2)) [1,2,3,4]
elfm . getCompose $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or with toList (method of Foldable)
toList $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or
toList $ (*2) <$> Compose (Just [1,2,3,4])
-- [2,4,6,8]
-- (>>*<<) flem (Just (++ "Haskell")) ["Hello, "]
flem . getCompose $ pure (++ "Haskell") <*> Compose (Just ["Hello, "])
-- or with toList and listToMaybe
listToMaybe . toList $ pure (++ "Haskell") <*> Compose (Just ["Hello, "])
-- or
listToMaybe . toList $ (++ "Haskell") <$> Compose (Just ["Hello, "])
-- or with head :: Foldable f => f a -> Maybe a
head $ (++ "Haskell") <$> Compose (Just ["Hello, "])
-- Just "Hello, Haskell"
关于最后一个问题。你在@Robin Zigmond 的评论中得到了答案。它写了关于 newtype ZipList
。使用 ZipList
你可以:
getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [4,5,6]
-- [5,7,9]
因此,Haskell 中 newtype 的目的之一是能够为某种类型编写不同的实例。
我是 Haskell 的新手。这可能是个愚蠢的问题。
因为 Applicative 类型类具有 apply 函数,该函数在同一上下文中获取函数和数据。为什么它不能与众不同并且更通用。
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
为什么我们不能写这样的东西
class Functor f => Applicative f where
(<*>) :: Functor g => g (a -> b) -> f a -> g (f b)
(<*>) gab fa = fmap (\g -> fmap g fa) gab
(<<*>>) :: Functor g => (g (f a) -> f a) -> g (a -> b) -> f a -> f b
(<<*>>) peelOuter gab fa = peelOuter $ gab <*> fa
(>>*<<) :: Functor g => (g (f a) -> g a) -> g (a -> b) -> f a -> g b
(>>*<<) cleanInner gab fa = cleanInner $ gab <*> fa
可以如下使用
-- Extract List from maybe
elfm :: Maybe [a] -> [a]
elfm Nothing = []
elfm (Just xs) = xs
-- Fuse List elements in Maybe []
flem :: Monoid a => Maybe [a] -> Maybe a
flem Nothing = mempty
flem (Just xs) = Just $ foldl (<>) mempty xs
Just (*2) <*> [1,2,3,4]
-- Just [2,4,6,8]
(<<*>>) elfm (Just (*2)) [1,2,3,4]
-- [2,4,6,8]
(>>*<<) flem (Just (++ "Haskell")) ["Hello, "]
-- Just "Hello, Haskell"
而且我读到 Applicatives 的全部意义在于 Functors 提升多参数函数的缺点。这样对吗?
而且我觉得功能应用不尽如人意
add :: Num a => a -> a -> a
add a b = a + b
-- I want to apply [1,2,3] as First arguments and [4,5,6] as 2nd arguments.
-- Like add 1 4, add 2 4, add 3 6
-- But it is give all possibilities of combinations like a tree
-- <*>
-- (+1) (+2) (+3)
-- (1+4)(1+5)(1+6) (2+4)(2+5)(2+6) (3+4)(3+5)(3+6)
并且还将它们与批处理进行了比较,但没有给出非常真实的例子。请为此提供示例。
Applicative
的每个实例都必须有自己的 <*>
实现。这就是为什么我们首先输入 classes 的原因。您的代码具有 class 本身定义的所有方法,没有留下任何实例。这意味着根本没有太多类型 class 。只有一堆通用函数。所有的肉都委托给('<<*>>)
和(>>*<<)
的参数peelOuter
和cleanInner
。让我们更仔细地看看它们。它们或多或少是对称的,所以 (<<*>>)
应该足够了。
(<<*>>) :: Functor g => (g (f a) -> f a) -> g (a -> b) -> f a -> f b
(<<*>>) peelOuter gab fa = peelOuter $ gab <*> fa
其实peelOuter
本来应该是class类型的方法,但是问题不止一个。
第一个问题涉及到两个仿函数,peelOuter
每个对仿函数需要单独实现。也就是说,我们这里有一个双参数类型 class ApplicativePair
,我们需要为每一对创建一个单独的实例。
第二个问题是peelOuter
不能对每一对真正的Applicative
仿函数都实现。不能从 Maybe (Id a)
中提取 Id a
,或者从 IO [a]
中提取 [a]
,或者...
更糟糕的是,当 f
和 g
是同一个函子时,它是否总是可实现的还不清楚。显然,当 f
是一个 monad 时,它就只是一个 join
。然而,并非所有的应用程序都是 monad,而 join
恰恰是应用程序缺乏成为 monad 的地方。所以 peelOuter
,即使这样的类型是可实现的,也会违反一些 monad 法则。那是一件坏事?不一定,如果它仍然遵循适用的法律。但是,您没有提供任何法律,只提供了一堆功能。
任何两个仿函数都是 Functor 和 Applicative 仿函数。这用 newtype Compose
编码,参见 Data.Functor.Compose
。
所以,你的例子可以通过newtype Compose
来解决。
-- Just (*2) <*> [1,2,3,4]
getCompose $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or
getCompose $ (*2) <$> Compose (Just [1,2,3,4])
-- Just [2,4,6,8]
-- (<<*>>) elfm (Just (*2)) [1,2,3,4]
elfm . getCompose $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or with toList (method of Foldable)
toList $ pure (*2) <*> Compose (Just [1,2,3,4])
-- or
toList $ (*2) <$> Compose (Just [1,2,3,4])
-- [2,4,6,8]
-- (>>*<<) flem (Just (++ "Haskell")) ["Hello, "]
flem . getCompose $ pure (++ "Haskell") <*> Compose (Just ["Hello, "])
-- or with toList and listToMaybe
listToMaybe . toList $ pure (++ "Haskell") <*> Compose (Just ["Hello, "])
-- or
listToMaybe . toList $ (++ "Haskell") <$> Compose (Just ["Hello, "])
-- or with head :: Foldable f => f a -> Maybe a
head $ (++ "Haskell") <$> Compose (Just ["Hello, "])
-- Just "Hello, Haskell"
关于最后一个问题。你在@Robin Zigmond 的评论中得到了答案。它写了关于 newtype ZipList
。使用 ZipList
你可以:
getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [4,5,6]
-- [5,7,9]
因此,Haskell 中 newtype 的目的之一是能够为某种类型编写不同的实例。