将混合的、可能适用的类型的参数应用于函数的最佳方法

Best way to apply arguments of mixed, possibly Applicative, types to a function

我对 Haskell 和函数式编程还很陌生,我最近一直在学习 Functors、Applicatives 和 Monad。虽然我似乎了解基础知识,但当某些参数的类型更改为 Applicative 时,我很难弄清楚 best/most 应用函数参数的惯用方式。考虑以下简单代码:

myfun :: Int -> Int -> Int -> Int
myfun a b c = a + b + c             -- lets pretend this does something more complicated

a = 5
b = 10
c = 20

result = myfun a b c

使用myfun计算结果相当简单。但是,随着我们的要求发生变化,我们的输入 abc 可能会变为 Maybe Int[Int] 而不是 Int。我们仍然可以通过执行以下操作之一使用未修改的 myfun

result = myfun <$> a <*> b <*> c   -- either like this
result = liftA3 myfun a b c        -- or like that

然而,在实践中,参数 abc 可能并不总是在同一个 Applicative 中,因此上述两种方法都不起作用.在不修改 myfun 函数的情况下仍然使它工作的最佳方法是什么?考虑 abc 的以下情况:

非常感谢任何见解!

这取决于你想要发生什么。可能没有任何通用的方法来组合不同的单子。通常,当您确实需要组合不同的 monad 时,您可以经常(总是?)使用 monad 转换器,但通常有更简单的解决方案。你提到的特定组合就是这种情况。

在所有这些特定情况下,您可以将其中一个 monad 转换为另一个。在下文中,我将举例说明如何做到这一点。

其中一些示例使用了 Data.Maybe 中的函数,因此我将从以下内容开始:

import Data.Maybe

第一个示例中不需要,但第二个和第三个示例中需要。

一些Int,一些Maybe Int

如果您有 IntMaybe Int 值的组合,解决方案很简单。只需将 Int 值提升到 Maybe Int。您可以为此使用 Justpure。这是一个使用 pure:

的例子
a1 = 5
b1 = Just 10
c1 = 20

result1 :: Maybe Int
result1 = myfun <$> pure a1 <*> b1 <*> pure c1

结果是Just 35.

一些Maybe Int,一些Either String Int

您可以通过将其中一个 monad 转换为另一个来重复此技巧。如果您有一个好的 String 可用于 Nothing 个案例,您可以将 Maybe Int 值转换为 Either String Int 值。您还可以通过丢弃 String 值将 Either String Int 值转换为 Maybe Int 值。

这是一个将 Maybe Int 转换为 Either String Int 的示例:

a2 = Just 5
b2 = Right 10
c2 = Left "Boo!"

result2 :: Either String Int
result2 = myfun <$> maybe (Left "No value") Right a2 <*> b2 <*> c2

此组合使用 Data.Maybe 中的 maybe 函数。结果是 Left "Boo!".

一些[Int],一些Maybe Int

您可以使用 maybeToList:

轻松地将 Maybe Int 变成 [Int]
a3 = [5, 10]
b3 = Nothing
c3 = Just 20

result3 :: [Int]
result3 = myfun <$> a3 <*> maybeToList b3 <*> maybeToList c3

这样做的结果是 [],因为 Nothing 转换为 [],这就是 Applicative 用于列表的方式。这可能不是你想要的,但我希望这些例子能激发你想出你想要的构图。

正如其他答案中提到的,在这里保留 Applictative 之间的区别可能没有多大意义,最好在将它们应用于 [= 之前​​将它们缩减为一个15=].

但有时保留这些区别很方便。好消息是 Applicatives compose,这意味着两个或更多 Applicative 的 "nesting" 总是可以被赋予一个 Applicative 实例。

例如,我们可以这样定义组合 Applicative

{-# LANGUAGE DerivingVia, TypeOperators #-}
import Data.Functor.Compose

newtype A a = A (Either String (Maybe [a]))
    deriving (Functor,Applicative) 
         via Either String `Compose` Maybe `Compose` [] 

我们正在使用 -XDerivingVia in our own auxiliary datatype to avoid having to work with nested Compose 新类型,这会有点麻烦。

Applicative 作文作品"from the outer layer inwards"。也就是说,如果有一个 Left somehwere,所有的计算都以 Left 结束。如果外层成功,那么我们合并内部Maybes,如果它们都变成Just,我们应用合并内部列表。

我们还需要一些乏味的样板文件:将函数注入我们的组合 Applicative:

liftL1 :: Either String a -> A a
liftL1 = A . fmap (pure . pure)

liftL2 :: Maybe a -> A a
liftL2 = A . pure . fmap pure

liftL3 :: [a] -> A a
liftL3 = A . pure . pure

投入使用:

a = Right 5
b = Just 10
c = [20]

result = liftA3 myfun (liftL1 a) (liftL2 b) (liftL3 c)

或者,使用 -XApplicativeDo

result = do
    a <- liftL1 $ Right 5
    b <- liftL2 $ Just 10
    c <- liftL3 $ [20]
    pure $ myfun a b c