发布于 2026-01-06 1 阅读
0

为什么选择应用型?

为什么选择应用型?

在学习 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
fmap 只是在上下文中应用函数。

顺便说一下,从现在开始我们将使用中缀版本

应用

就应用程序而言,这一点并不明确。或者至少我花了更长时间才明白。

(<*>) :: 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函数需要两个参数(或者更准确地说,一次一个参数)。但我们只提供了一个参数(来自IntMaybe Int,所以它只被部分应用,并返回一个函数(Int -> Int)。所以bInt -> 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