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



我对Haskell和函数式编程相当陌生,最近我一直在学习函子,应用器和Monads。虽然我似乎了解基础知识,但当某些参数的类型更改为应用程序时,我很难弄清楚应用函数参数的最佳/最惯用方法。请考虑以下简单代码:

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的参数可能并不总是在同一应用范围内,因此上述两种方法将不起作用。在不修改myfun函数的情况下仍然工作的最佳方法是什么?考虑以下abc方案:

  • 有些是Int的,有些是Maybe Int的(申请结果将是Maybe Int(
  • 有些是Maybe Int的,有些是Either String Int的(结果可以是Maybe IntEither String Int,如果任何参数NothingLeft,则具有短路计算的语义(
  • 有些是[Int]的,有些是Maybe Int的(结果应该是Maybe [Int]的,语义是计算所有可能的组合,就好像所有的参数都被[Int]一样,然后将其包装在一个Just内,除非在Maybies上Nothing,在这种情况下我们短路到Nothing(

任何见解都非常感谢!

这取决于你想要发生的事情。可能没有任何通用的方法可以组合不同的单子。一般来说,当你真的需要组合不同的单子时,你经常(总是?(使用单子变压器,但通常有更简单的解决方案。您提到的特定组合就是这种情况。

在所有这些特定情况下,您可以将其中一个 monad 转换为另一个。在下文中,我将给出一些可以做到这一点的示例。

其中一些示例使用Data.Maybe中的函数,所以我将从:

import Data.Maybe

在第一个示例中不是必需的,但在第二个和第三个示例中是必需的。

有些Int,有些Maybe Int

如果您有Int值和Maybe 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

您可以重复将其中一个单子转换为另一个的技巧。如果您有用于Nothing情况的良好String,则可以Maybe Int值转换为Either String Int值。您还可以通过丢弃StringEither 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之间的区别可能没有多大意义,最好将它们减少为一个,然后再将它们应用于myfun

但有时保留区别很方便。好消息是Applicative组成,这意味着两个或多个Applicative的"嵌套"总是可以给出一个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以避免使用嵌套Composenewtype,这会更麻烦一些。

Applicative构图"从外层向内"工作。也就是说,如果某处有Left,则所有计算都以Left结束。如果外层成功,那么我们组合内Maybe,如果它们都变成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

相关内容

最新更新