为什么选择应用型?
在学习 Haskell 中的不同类型类时,我遇到的最大困难是Applicative 类型。
函子
函子在某种程度上是直截了当的。我们取任意一元函数,并使它们在某种(函子)上下文中发挥作用。
fmap :: Functor f => (a -> b) -> f a -> f b
-- or the infix version
(<$>) :: Functor f => (a -> b) -> f a -> f b
假设我们有一个作用于Ints 的递增函数。
inc :: Int -> Int
λ> inc 1
2
通过使用,fmap我们可以映射任何包含 的函子Int。
λ> fmap inc [1, 2, 3]
[2, 3, 4]
λ> fmap inc (Just 1)
Just 2
λ> fmap inc (Right 1)
Right 2
当我们fmap与函数应用运算符对齐时,这一点就变得非常清楚了。
($) :: (a -> b) -> a -> b
(<$>) :: Functor f => (a -> b) -> f a -> f b
inc $ 1 -- 2
inc <$> [1] -- [2]
inc <$> (Just 1) -- Just 2
顺便说一下,从现在开始我们将使用中缀版本。
应用
就应用程序而言,这一点并不明确。或者至少我花了更长时间才明白。
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
这样做并($)没有什么帮助。我为什么要在上下文中也包含这个函数呢?
($) :: (a -> b) -> a -> b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
我们说过函子允许在上下文中应用一元函数。但如果我想应用一个元数更高的函数会发生什么呢?
add :: Int -> Int -> Int
add <$> (Just 1) -- ??
REPL 显示什么?🦊
λ> :t add <$> (Just 1)
add <$> (Just 1) :: Maybe (Int -> Int)
Maybe (Int -> Int)是的,我们已经在签名里看到了(<*>)。
add <$> (Just 1) <*> (Just 2) -- Just 3
让我们来剖析一下这个🔍
-- refresh these ones first :)
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
-- With Maybe applied (using TypeApplications extension)
(<$>) @Maybe :: (a -> b) -> Maybe a -> Maybe b
add :: Int -> Int -> Int
-- With Int applied in place of 'a'
(<$>) @Maybe @Int :: (Int -> b) -> Maybe Int -> Maybe b
-- With (Int -> Int) applied in place of 'b'
(<$>) @Maybe @Int @(Int -> Int)
-- (a -> b) -> Maybe a -> Maybe b
:: (Int -> Int -> Int) -> Maybe Int -> Maybe (Int -> Int)
add <$> (Just 1) :: Maybe (Int -> Int)
这里我们看到了第一个有趣的地方。因为我们的add函数需要两个参数(或者更准确地说,一次一个参数)。但我们只提供了一个参数(来自Int)Maybe Int,所以它只被部分应用,并返回一个函数(Int -> Int)。所以b是Int -> Int。
-- a -> b
add :: Int -> (Int -> Int)
注意,实际上不需要括号,因为箭头 ( ->) 是右结合的。
那是表达式的第一部分。我们缺少谓语动词。
(<*>) @Maybe :: Maybe (c -> d) -> Maybe c -> Maybe d
-- With Int in place of 'c'
(<*>) @Maybe @Int :: Maybe (Int -> b) -> Maybe Int -> Maybe b
-- And also Int in place of 'd'
(<*>) @Maybe @Int @Int
:: Maybe (Int -> Int) -> Maybe Int -> Maybe Int
瞧!
add :: Int -> Int -> Int
add <$> (Just 1) :: Maybe (Int -> Int)
add <$> (Just 1) <*> (Just 2) :: Maybe Int
函子:在上下文中应用一元函数。
应用式:在上下文中应用n 元函数。
在 Haskell 中,这被称为lift 。
在这种情况下应用函数的关键在于它们所关联的语义。它可以用于验证、可选值(不带null😏符号)、项目列表或树、运行IO操作、解析器等。
当上下文为“可能”时:
λ> add <$> (Just 1) <*> (Just 2)
Just 3
λ> add <$> Nothing <*> (Just 2)
Nothing
λ> add <$> (Just 1) <*> Nothing
Nothing
λ> add <$> Nothing <*> Nothing
Nothing
当上下文为“两者之一”时:
λ> add <$> (Right 1) <*> (Right 2)
Right 3
λ> add <$> (Left "err 1") <*> (Right 2)
Left "err 1"
λ> add <$> (Right 1) <*> (Left "err 2")
Left "err 2"
λ> add <$> (Left "err 1") <*> (Left "err 2")
Left "err 1"
当上下文为列表时:
λ> add <$> [1, 2, 3] <*> [1, 2, 3]
[2,3,4,3,4,5,4,5,6]
λ> (,) <$> [1, 2, 3] <*> [1, 2, 3]
[(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]
☝️更多内容将在下一期中介绍。
结论
学习函数式编程时,所有这些类型类看起来可能令人生畏。培养对它们用途和用法的基本直觉是熟练使用(和理解)它们的关键一步。
我知道函子和应用类型还有很多我尚未发现的含义。但任何学习过程都需要时间。我相信随着学习的深入,更多的事情会逐渐明朗,并被我理解。
今天就到这里吧。
快乐安全的编程体验🎉
文章来源:https://dev.to/gillchristian/why-applicative-kao