将混合的、可能适用的类型的参数应用于函数的最佳方法
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
计算结果相当简单。但是,随着我们的要求发生变化,我们的输入 a
、b
和 c
可能会变为 Maybe Int
或 [Int]
而不是 Int
。我们仍然可以通过执行以下操作之一使用未修改的 myfun
:
result = myfun <$> a <*> b <*> c -- either like this
result = liftA3 myfun a b c -- or like that
然而,在实践中,参数 a
、b
和 c
可能并不总是在同一个 Applicative 中,因此上述两种方法都不起作用.在不修改 myfun
函数的情况下仍然使它工作的最佳方法是什么?考虑 a
、b
和 c
的以下情况:
- 有些是
Int
,有些是 Maybe Int
(申请结果是 Maybe Int
)
- 有些是
Maybe Int
,有些是 Either String Int
(结果可能是 Maybe Int
或 Either String Int
,如果任何参数是 Nothing
或 Left
)
- 有些是
[Int]
,有些是 Maybe Int
(结果应该是 Maybe [Int]
,计算所有可能组合的语义就好像所有参数都是 [Int]
,然后将其包装在 Just
内,除非 Maybies 中的一个是 Nothing
,在这种情况下我们将短路到 Nothing
)
非常感谢任何见解!
这取决于你想要发生什么。可能没有任何通用的方法来组合不同的单子。通常,当您确实需要组合不同的 monad 时,您可以经常(总是?)使用 monad 转换器,但通常有更简单的解决方案。你提到的特定组合就是这种情况。
在所有这些特定情况下,您可以将其中一个 monad 转换为另一个。在下文中,我将举例说明如何做到这一点。
其中一些示例使用了 Data.Maybe
中的函数,因此我将从以下内容开始:
import Data.Maybe
第一个示例中不需要,但第二个和第三个示例中需要。
一些Int
,一些Maybe Int
如果您有 Int
和 Maybe Int
值的组合,解决方案很简单。只需将 Int
值提升到 Maybe Int
。您可以为此使用 Just
或 pure
。这是一个使用 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=].
但有时保留这些区别很方便。好消息是 Applicative
s 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
结束。如果外层成功,那么我们合并内部Maybe
s,如果它们都变成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
我对 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
计算结果相当简单。但是,随着我们的要求发生变化,我们的输入 a
、b
和 c
可能会变为 Maybe Int
或 [Int]
而不是 Int
。我们仍然可以通过执行以下操作之一使用未修改的 myfun
:
result = myfun <$> a <*> b <*> c -- either like this
result = liftA3 myfun a b c -- or like that
然而,在实践中,参数 a
、b
和 c
可能并不总是在同一个 Applicative 中,因此上述两种方法都不起作用.在不修改 myfun
函数的情况下仍然使它工作的最佳方法是什么?考虑 a
、b
和 c
的以下情况:
- 有些是
Int
,有些是Maybe Int
(申请结果是Maybe Int
) - 有些是
Maybe Int
,有些是Either String Int
(结果可能是Maybe Int
或Either String Int
,如果任何参数是Nothing
或Left
) - 有些是
[Int]
,有些是Maybe Int
(结果应该是Maybe [Int]
,计算所有可能组合的语义就好像所有参数都是[Int]
,然后将其包装在Just
内,除非 Maybies 中的一个是Nothing
,在这种情况下我们将短路到Nothing
)
非常感谢任何见解!
这取决于你想要发生什么。可能没有任何通用的方法来组合不同的单子。通常,当您确实需要组合不同的 monad 时,您可以经常(总是?)使用 monad 转换器,但通常有更简单的解决方案。你提到的特定组合就是这种情况。
在所有这些特定情况下,您可以将其中一个 monad 转换为另一个。在下文中,我将举例说明如何做到这一点。
其中一些示例使用了 Data.Maybe
中的函数,因此我将从以下内容开始:
import Data.Maybe
第一个示例中不需要,但第二个和第三个示例中需要。
一些Int
,一些Maybe Int
如果您有 Int
和 Maybe Int
值的组合,解决方案很简单。只需将 Int
值提升到 Maybe Int
。您可以为此使用 Just
或 pure
。这是一个使用 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=].
但有时保留这些区别很方便。好消息是 Applicative
s 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
结束。如果外层成功,那么我们合并内部Maybe
s,如果它们都变成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