我正在尝试过滤类型列表:IO [Either a b]
理想情况下,我想用以下类型的sig组成过滤函数:
(Monad m, Monad m2) => m [m2 a] -> (a -> Bool) -> m [m2 a]
我尝试了很多filter
,filterM
,fmap
和=<<
的组合,徒劳地试图将我的谓词提升到适当的语境中,但我保持错过标记 - 我可以实现m [m a]-> (a -> m Bool)-> m [m a]
,但由于Either和IO不是同一个monad,这似乎对我没有好处。
这似乎是'do notation'的用例,但是我无法用一种方法来检查用<-
运算符分配的事物的类型签名,所以我在移动时离开了目标
我不确定如何以一种明确表示它正在遍历包含不同monad(Either)实例的列表然后包含列表本身(IO)的monad的方式来编写函数。
我在这里缺少什么?
答案 0 :(得分:5)
理想情况下,我想用以下内容组成过滤功能 type sig:
(Monad m, Monad m2) => m [m2 a] -> (a -> Bool) -> m [m2 a]
一般情况下无法完成,因为Monad
界面无法从a
中获取m a
。您可以使用m [m2 Bool]
获取fmap (fmap (fmap f))
,但您需要从Bool
中获取m2 Bool
才能决定是否删除元素。
以这种方式看待它。如果有问题的monad是Proxy
怎么办?
data Proxy a = Proxy -- one constructor, no fields
instance Monad Proxy where
return _ = Proxy
Proxy >>= _ = Proxy
你无法查看Bool
内的Proxy Bool
,因为它里面什么都没有!鉴于a -> Bool
和[Proxy a]
,如何才能知道要删除列表中的哪些元素?
你可以编写一个具有以下类型签名的函数:
myfilter :: (Functor f, Applicative g) => (a -> Bool) -> f [g a] -> f (g [a])
请注意,返回类型为f (g [a])
,而不是f [g a]
。
此功能使用fmap
进入外部仿函数,将列表中的应用g
粉碎在一起,然后再次fmap
来过滤结果。
myfilter p = fmap (fmap (filter p) . sequenceA)
答案 1 :(得分:1)
如果您可能无法从a
中获取Monad m
,则可以添加到已接受的答案中,您可以使用例如Monad m
将您的功能添加到liftM
。 IO[Either a b]
。根据内部结构,你仍然可以编写可用的动作,然后最终以某种方式对它们进行排序,这样计算就会发生。以期望的方式。
举例说明使用IO[Either Bool Integer]
作为import Control.Monad
testCase :: IO [Either Bool Integer]
testCase = return [(Right 1), (Left True), (Right 2), (Left False), (Right 3), (Right 4)]
liftFilter :: Monad m => (a -> Bool) -> m [a] -> m [a]
liftFilter pred = liftM (filter pred)
testFilter :: (Either Bool Integer) -> Bool
testFilter (Left True) = True
testFilter (Right x) = even x
testFilter _ = False
showIt :: (Either Bool Integer) -> String
showIt (Left True) = "Left True"
showIt (Left False) = "Left False"
showIt (Right x) = "Right x=" ++ (show x)
test = do
filtered <- liftFilter testFilter testCase
return (fmap showIt filtered)
runTest = do
actions <- liftM (fmap putStrLn) test
sequence_ actions
的问题:
composer.json
答案 2 :(得分:0)
我正在使用其monad实例而不是使用filter方法来处理列表。在现实世界中使用过滤器可能会更好。我也没有试图使我的功能尽可能小
我假设您想要一些任意方法来处理Eithers。 如果只想过滤“右边的a”或获取左边的颜色,则可能应该使用遍历。您还可以使用bimap来同时“左右”映射Either的左侧和右侧。
首先是仅适用于either
的解决方案:
filtermonads :: IO [(Either a b)] -> (a -> Bool) -> IO [(Either a b)]
filtermonads ioListEitherA cond = do
listEitherA <- ioListEitherA
return $ do
eitherA <- listEitherA
case eitherA of
Left a -> if cond a then pure (Left a) else []
Right b -> return (Right b)
应与以下相同:
filtermonads' :: IO [(Either a b)] -> (a -> Bool) -> IO [(Either a b)]
filtermonads' ioListEitherA cond = let
f x = case x of
Left a -> if cond a then pure (Left a) else []
Right a -> pure (Right a)
in
fmap (join . fmap f) ioListEitherA
我们必须手动打开Either,因为我们需要访问其中封装的信息才能知道如何处理列表。
您可以通过单独提供一个函数(m2 a -> Bool
)来概括这一点,该函数可以告诉您如何从monad中提取信息。
eitherCond :: Either a b -> Bool
eitherCond eitherA = case eitherA of
Left a -> False
Right b -> True
filtermonads'' :: (Monad m) => m [a] -> (a -> Bool) -> m [a]
filtermonads'' m1listm2a cond = do
listm2a <- m1listm2a
return $ do
m2a <- listm2a
if cond m2a then pure m2a else []
您还可以将列表概括为MonadPlus
或MonadFail
,因为您可以想到过滤意味着您需要一个零元素的monad。 monad库本身的失败有时为零,但您不应该使用它,因为它不是普通monad所具有的功能,因此实现有时是非常任意的。
纯过滤器可能更好,因为只有很少的结构实现MonadPlus
类。我应该为此使用mfilter
。 MonadPlus
也没有太多实现它的结构。例如,地图甚至不是单子。
filtermonads''' :: (Monad m1, MonadPlus mp) => m1 (mp a) -> (a -> Bool) -> m1 (mp a)
filtermonads''' m1listm2a cond = do
listm2a <- m1listm2a
return $ do
m2a <- listm2a
if cond m2a then pure m2a else mzero
testData :: IO [Either Char Char]
testData = pure [Right 'a', Left 'a', Left 'c']
test1 = filtermonads testData (\x -> x=='a')
test2 = filtermonads' testData (\x -> x=='a')
test3 = filtermonads'' testData eitherCond
test4 = filtermonads''' testData eitherCond
justData ::IO (Maybe Char)
justData = pure (Just 'b')
readData :: Maybe (IO Char)
readData = Just (getChar) -- Notice that io mzero is error!
test5 = filtermonads''' justData (\x -> x=='a')
test6 = filtermonads''' readData (\x -> x=='a')
main = case (test6) of
Just a -> a >>= putStrLn . pure
Nothing -> pure ()