【问题标题】:Examples of Haskell Applicative TransformersHaskell 应用变压器的例子
【发布时间】:2012-09-25 16:27:06
【问题描述】:

www.haskell.org 上的 wiki 告诉我们以下有关 Applicative Transformers 的信息:

那么应用转换器在哪里呢?答案是,我们不需要针对应用函子的特殊转换器,因为它们可以以通用方式组合。 http://www.haskell.org/haskellwiki/Applicative_functor#Applicative_transfomers

我尝试了以下方法来尝试组合一堆应用函子。但我得到的只是一堆错误。代码如下:

import Control.Applicative
import System.IO

ex x y = (:) <$> x <*> y 
test1 = ex "abc" ["pqr", "xyz"]  -- only this works correctly as expected
test2 = ex "abc" [Just "pqr", Just "xyz"]
test3 = ex "abc" (Just "pqr")
test4 = ex (Just 'a') ["pqr", "xyz"]
test5 = ex (return ("abc"):: IO ()) [Just "pqr", Just "xyz"]

这会产生很多类型错误,虽然我可以部分理解,但我根本无法解决。

错误在最后给出。

那么,例如,我如何将 Maybe Applicative 和 List Applicative 结合起来?

例如,我如何结合 State Applicative 和 List Applicative? 还有其他例子吗,比如说,结合了 Maybe 和 List,Maybe 和 State,最后是可怕的 IO 和 State 应用程序?

谢谢。

GHCi 错误消息如下。

example.hs:6:19:
    Couldn't match expected type `[Char]' with actual type `Maybe a0'
    In the return type of a call of `Just'
    In the expression: Just "pqr"
    In the second argument of `ex', namely `[Just "pqr", Just "xyz"]'

example.hs:7:19:
    Couldn't match expected type `[[Char]]' with actual type `Maybe a0'
    In the return type of a call of `Just'
    In the second argument of `ex', namely `(Just "pqr")'
    In the expression: ex "abc" (Just "pqr")

example.hs:8:23:
    Couldn't match expected type `Maybe' with actual type `[]'
    In the second argument of `ex', namely `["pqr", "xyz"]'
    In the expression: ex (Just 'a') ["pqr", "xyz"]
    In an equation for `test4': test4 = ex (Just 'a') ["pqr", "xyz"]

example.hs:9:21:
    Couldn't match expected type `()' with actual type `[Char]'
    In the first argument of `return', namely `("abc")'
    In the first argument of `ex', namely `(return ("abc") :: IO ())'
    In the expression:
      ex (return ("abc") :: IO ()) [Just "pqr", Just "xyz"]
Failed, modules loaded: none.
Prelude>

【问题讨论】:

  • 请注意,使用应用程序时不再需要转换器并不完全正确。例如。 StateT s IO aApplicative,但它不是任何两个应用程序的组合。

标签: haskell monad-transformers applicative


【解决方案1】:

考虑以下类型签名:

liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b

结合起来,得到的类型是:

liftA2 (<*>) :: (Applicative f, Applicative g) 
             => f (g (a -> b)) -> f (g a) -> f (g b)

这确实是两个Applicatives 的组合。事实上,它是恰好两个Applicatives 的组合。换句话说,尽管您可以以通用方式组合Applicatives,但这绝不是自动完成的。一切都必须明确提升正确的次数。

您的ex 函数等价于liftA2 (:),其类型为(Applicative f) =&gt; f a -&gt; f [a] -&gt; f [a]。浏览您的示例,对您想要做什么做出一些猜测:

test1 = ex "abc" ["pqr", "xyz"]

这里f[],我们将它应用于[Char][[Char]] 类型的参数。

test2 = ex "abc" [Just "pqr", Just "xyz"]

第二个参数的类型是[Maybe [Char]],所以我们需要提升两次。第一个参数也需要解除,因为它的类型为[Char],应该是[Maybe Char]

test3 = ex "abc" (Just "pqr")

这次第二个参数的类型是Maybe [Char],所以fMaybe,我们只需要一个电梯。因此,第一个参数的类型应该是Maybe Char

test4 = ex (Just 'a') ["pqr", "xyz"]

这次第一个参数是Maybe Char,但第二个是[[Char]],所以你有两个完全不同的Applicatives;两者都需要解除,给你[Maybe Char]Maybe [Char]

test5 = ex (return ("abc"):: IO ()) [Just "pqr", Just "xyz"]

这里的类型签名没有意义;你可能想要IO [Char]。第二个参数的类型为[Maybe [Char]]。与前面的示例一样,它们不匹配,但这次您有 三个 Applicatives。如果您想要IO [Maybe a] 之类的东西,则需要将(:) 全部提升三次,例如liftA2 (liftA2 ex).

这种组合Applicatives 的方式称为“函子组合”,您链接到的页面提到了定义显式组合类型构造函数的库。例如,using the transformers library,您可以使用类似Compose IO (Compose [] Maybe) 的类型来描述您的第五个示例。这种组合类型以上述通用方式定义为Applicative 实例,并应用正确数量的提升操作。缺点是您需要包装和解开这需要的 newtype 层。


作为附录,此声明:

那么应用转换器在哪里呢?答案是,我们不需要针对应用函子的特殊转换器,因为它们可以以通用方式组合。

...有点假。的确,两个Applicatives 的组合也是Applicative,但这并不是组合Applicatives 的唯一方法!

考虑StateT s m a,它等同于s -&gt; m (s, a),尽管它的定义略有不同。这也可以写成三个函子的组合:((-&gt;) s)m((,) s),生成的 Functor 实例是正确的,但 Applicative 实例将是完全 em>错了。如果您只是从 State s a = s -&gt; (a, s) 开始,则无法通过组合 State sm 来定义 StateT s m

现在,请注意非组合组合 StateT s (Either e) 本质上是 Parsec 等库中使用的典型解析器组合器 monad 的简化版本,此类解析器是使用 Applicative 样式的著名地方之一受欢迎的。因此,就Applicative 而言,暗示monad 转换器风格的组合在某种程度上是不必要或多余的,这似乎有点误导!

【讨论】:

  • 类型签名错误?不应该是liftA2 (&lt;*&gt;) = f (g (a -&gt; b)) -&gt; f (g a) -&gt; f (g b) 吗?
  • 看看使用Compose 的例子会很有趣,看看它给代码增加了多少句法开销。
【解决方案2】:

维基文章说liftA2 (&lt;*&gt;) 可用于组成应用函子。从它的类型很容易看出怎么用:

o :: (Applicative f, Applicative f1) =>
     f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
o = liftA2 (<*>)

所以如果fMaybe 并且f1[] 我们得到:

> Just [(+1),(+6)] `o` Just [1, 6] 
Just [2,7,7,12]

反之亦然:

>  [Just (+1),Just (+6)] `o` [Just 1, Just 6]
[Just 2,Just 7,Just 7,Just 12]

正如@McCann 所说,您的 ex 函数等同于 liftA2 (:)

test1 = liftA2 (:) "abc" ["pqr", "xyz"]

要将(:) 与更深的应用堆栈一起使用,您需要liftA2 的多个应用:

*Main> (liftA2 . liftA2) (:) (Just "abc") (Just ["pqr", "xyz"])
Just ["apqr","axyz","bpqr","bxyz","cpqr","cxyz"]

但它仅在两个操作数的深度相同时才有效。所以除了双 liftA2 你应该使用 pure 来修复水平:

*Main> (liftA2 . liftA2) (:) (pure "abc") (Just ["pqr", "xyz"])
Just ["apqr","axyz","bpqr","bxyz","cpqr","cxyz"]

【讨论】:

    【解决方案3】:

    说它们可以以通用方式组合并不意味着它们可以隐式或不可见或类似的方式组合。 =)

    您仍然需要编写一些代码,或者使用与&lt;*&gt;pure 不同的芒格,或者添加一些新类型的噪音。例如,使用TypeCompose 包,您可以编写

    test2 = ex (O (Just "abc")) [O (Just "pqr"), O (Just "xyz")]
    

    【讨论】:

    • Data.Functor.Compose 来自 transformers 包可能是更常用的数据类型。它还有Data.Functor.Product,这是一种组合应用函子的不同方式。
    • 更多关于 TypeCompose 的描述可以在wiki.haskell.org/TypeCompose找到
    【解决方案4】:

    像往常一样,在这里关注类型以弄清楚应用函子的组合应该意味着什么是有用的。

    如果我们为特定纯值x 的类型写a,因此没有副作用,那么我们可以使用pure 将此纯值提升为应用函子f 的计算组合器。但出于同样的原因,我们可以使用gApplicative 实例中的pure 函数将pure x 提升到g 函子中。

    pure (pure x) :: g (f a)
    

    现在g (f a) 是结合g 的效果和f 的效果的计算类型。查看您的测试,我们注意到

    test1 :: [String]
    

    你在test1 中只使用了一种效果,那就是Applicative 的列表实例给你的非确定性。确实,分解它:

    "abc" :: String
    ((:) <$>) :: [Char] -> [String -> String]
    ((:) <$> "abc") :: [String -> String]
    ((:) <$> "abc" <*> ["pqr", "xyz"]) :: [String]
    

    现在,如果我们想组合失败效应和非确定性效应,我们希望构造一个Maybe [a][Maybe a] 类型的计算。事实证明这两者是等价的,因为应用函子总是commute

    这是[Maybe Char] 类型的计算。它会不确定地返回Char,但如果返回,它可能会失败:

    x1 = [Just 'a', Just 'b', Just 'c']
    

    同样,这是[Maybe String] 类型的计算:

    x2 = [Just "pqr", Just "xyz"]
    

    现在我们想将(:) 提升到这个组合的应用函子。为此,我们必须将其抬起两次:

    pure (pure (:)) :: [Maybe (Char -> String -> String)]
    

    同样,要应用它,我们需要通过两个函子推动这个计算。因此,我们可以引入一个新的组合器 (&lt;&lt;*&gt;&gt;) 来实现这一点:

    (<<*>>) :: (Applicative f, Applicative f1) =>
               f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
    (<<*>>) = liftA2 (<*>)
    

    现在我们可以写:

    pure (pure (:)) <<*>> x1 <<*>> x2
    

    您可以检查它是否具有预期的类型。

    但由于应用函子在组合下是封闭的,[Maybe a] 本身就是一个应用函子,因此您可能希望能够重用 pure(&lt;*&gt;)transformers 包的 Data.Functor.Compose 模块向您展示了如何操作。

    【讨论】:

    • 看看一个使用 Compose 的例子会很有趣,看看它给代码增加了多少句法开销。
    猜你喜欢
    • 2015-09-28
    • 1970-01-01
    • 2022-12-02
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-14
    相关资源
    最近更新 更多