Я изучал реализации ListT, чтобы найти хороший способ сделать что-то вроде этого:
func list = do
set <- get -- Data.Set
el <- list
guard $ Set.notMember el set
return el
Я знаю, что мог бы использовать ListT.fromFoldable, но я хочу иметь возможность использовать этот поток как часть более крупного конвейера обработки без преобразования из списка в поток и обратно на каждом этапе. Возможно, что-то вроде этого:
func' list = (ListT.toReverseList . ListT.take 5 . func . ListT.fromFoldable) list
Насколько я понимаю, здесь следует использовать потоковый подход. Но я не понимаю, как это сделать, например. пакет list-t. Может ли обход каким-то образом отфильтровать результаты из потока? Я не вижу, чтобы люди спрашивали об этом, так что, может быть, сам подход ошибочен?
Отвечая на исходный вопрос перед редактированием:
Насколько я понимаю, здесь следует использовать потоковый подход. Но я не понимаю, как это сделать, например. пакет
list-t
.
Вы можете использовать обычные списки, если ваша монада достаточно ленива:
import Control.Monad (filterM)
import Control.Monad.Trans.State.Lazy (State, get)
import Data.Set (Set, member)
filterStateLazy :: Ord a => [a] -> State (Set a) [a]
filterStateLazy = filterM $ \x -> do
s <- get
pure $ x `member` s
-- >>> import Control.Monad.Trans.State.Lazy (evalState)
-- >>> import qualified Data.Set as Set
-- >>> take 5 . evalState (filterStateLazy [1..]) $ Set.fromList [1, 6, 22, 39, 54]
-- [1,6,22,39,54]
Если вы действительно хотите ListT
, вы также можете использовать его, и вам не понадобится ленивая монада:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
import ListT
import Control.Monad.State.Strict
import Data.Set (Set, member)
filterStateLazy :: (MonadState (Set a) m, Ord a) => ListT m a -> ListT m a
filterStateLazy (ListT run) = ListT $ run >>= \case
Nothing -> return Nothing
Just (x, rest) -> do
s <- get
if x `member` s
then pure $ Just (x, filterStateLazy rest)
else uncons $ filterStateLazy rest
-- >>> import Control.Monad.Trans.State.Lazy (evalState)
-- >>> import qualified ListT as ListT
-- >>> import qualified Data.Set as Set
-- >>> evalState (ListT.toList . ListT.take 5 . filterStateLazy $ ListT.fromFoldable [1..]) $ Set.fromList [1, 6, 22, 39, 54]
-- [1,6,22,39,54]
Хотя ответ @effectfully работает нормально, вы, вероятно, ищете mfilter
от Control.Monad
:
func :: (MonadPlus m, MonadState (Set a) m, Ord a) => m a -> m a
func act = do
st <- get
mfilter (`Set.notMember` st) act
Если ваша функция фильтрации сама по себе является монадической, вам может потребоваться определить свою собственную:
mfilterM :: (MonadPlus m) => (a -> m Bool) -> m a -> m a
mfilterM f act = do
a <- act
b <- f a
if b then pure a else empty
что, например, позволит вам написать функцию, которая отфильтровывает список видимых на данный момент значений, сохраняя набор предыдущих значений в состоянии:
unique :: (MonadPlus m, MonadState (Set a) m, Ord a) => m a -> m a
unique = mfilterM firstTime
where firstTime x = do
isnew <- gets (Set.notMember x)
when isnew $ modify (Set.insert x)
pure isnew
В контексте и заимствовании примера @effectfull:
import ListT
import Control.Monad
import Control.Monad.State
import Control.Applicative
import Data.Set (Set)
import qualified Data.Set as Set
func1 :: (MonadPlus m, MonadState (Set a) m, Ord a) => m a -> m a
func1 act = do
st <- get
mfilter (`Set.notMember` st) act
mfilterM :: (MonadPlus m) => (a -> m Bool) -> m a -> m a
mfilterM f act = do
a <- act
b <- f a
if b then pure a else empty
unique :: (MonadPlus m, MonadState (Set a) m, Ord a) => m a -> m a
unique = mfilterM firstTime
where firstTime x = do
isnew <- gets (Set.notMember x)
when isnew $ modify (Set.insert x)
pure isnew
runM :: ListT (State (Set a)) a -> Set a -> [a]
runM act state0 = evalState (ListT.toList act) state0
main :: IO ()
main = do
print $ runM (ListT.take 5 . func1 . ListT.fromFoldable $ [1..]) (Set.fromList [1,6,22,39,54])
print $ runM (unique . ListT.fromFoldable $ [1,2,3,4,5,1,3,6,5,7]) (Set.empty)
Ваш вариант отличается тем, что mfilter
принимает чистую функцию, в отличие от той, что есть в моих решениях. В данном примере это не имеет значения, но в каком-то другом может быть. В любом случае, хорошее решение.
Истинный. Я добавил новую функцию mfilterM
и пример для решения этой проблемы.
Отлично, я удивлен, что это можно выразить так прямолинейно. Спасибо!
Я добавил
ListT
решение к своему ответу.