6

私は基本的にmapMリストのような関数を探しています - リスト内のすべての値をパラメーターとして取り、一連のモナド アクションを実行し、各モナド関数が戻りますm (Maybe b)。ただし、関数が値を返すようにする最初のパラメーターの後に停止し、Justそれ以降は実行せず、その値を返すようにしたいと考えています。

まあ、おそらく型シグネチャを表示する方が簡単でしょう:

findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)

ここで、b は最初のJust値です。Maybe結果の は ing からのもの(空のリストなどの場合) であり、Monadic 関数によって返されるfindとは何の関係もありません。Maybe

ライブラリ関数の単純なアプリケーションでこれを実装することはできないようです。私は使用できます

findM f xs = fmap (fmap fromJust . find isJust) $ mapM f xs

これは機能しますが、これをテストしたところ、 を呼び出す前にすべてのモナド アクションが実行されているように見えるfindため、ここでは怠惰に頼ることはできません。

ghci> findM (\x -> print x >> return (Just x)) [1,2,3]
1
2
3
-- returning IO (Just 1)

最初の "just" return の後にモナド アクションを実行しないこの関数を実装する最良の方法は何ですか? すること:

ghci> findM (\x -> print x >> return (Just x)) [1,2,3]
1
-- returning IO (Just 1)

または、理想的には、

ghci> findM (\x -> print x >> return (Just x)) [1..]
1
-- returning IO (Just 1)

うまくいけば、明示的な再帰を使用しない答えがあり、可能であればライブラリ関数の構成ですか? それともポイントフリーのものでしょうか?

4

4 に答える 4

17

簡単で無意味な解決策の 1 つは、MaybeT変圧器を使用することです。いつでもそれをm (Maybe a)ラップすることができMaybeT、すべてのMonadPlus関数をすぐに取得できます。mplusforMaybeTはまさに私たちが必要としているものなので、最初のアクションの結果が得られた場合にのみ、2 番目に指定されたアクションを実行します。まさに必要Nothingmsumことを行います。

import Control.Monad
import Control.Monad.Trans.Maybe

findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM f = runMaybeT . msum . map (MaybeT . f)

更新:この場合、必要なセマンティックを持つモナド変換子 ( MaybeT)が存在することは幸運でした。mplusしかし、一般的なケースでは、そのような変圧器を構築することは不可能である可能性があります. MonadPlusには、他のモナド操作に関して満たさなければならない法則がいくつかあります。しかし、実際には は必要ないので、すべてが失われるわけではありません。必要なのは、折りたたむMonadPlusための適切なモノイドだけです。

では、 を持っていない (できない) ことにしましょうMaybeT。一連の操作の最初の値の計算は、Firstモノイドによって記述されます。左部分に値がある場合、右部分を実行しないモナドバリアントを作成する必要があるだけです。

newtype FirstM m a = FirstM { getFirstM :: m (Maybe a) }
instance (Monad m) => Monoid (FirstM m a) where
    mempty = FirstM $ return Nothing
    mappend (FirstM x) (FirstM y) = FirstM $ x >>= maybe y (return . Just)

このモノイドは、リストやその他の構造への参照なしでプロセスを正確に記述します。次に、このモノイドを使用してリストを折りたたむだけです。

findM' :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM' f = getFirstM . mconcat . map (FirstM . f)

さらに、次を使用して、より一般的な (さらに短い) 関数を作成できますData.Foldable

findM'' :: (Monad m, Foldable f)
        => (a -> m (Maybe b)) -> f a -> m (Maybe b)
findM'' f = getFirstM . foldMap (FirstM . f)
于 2013-09-01T08:12:21.327 に答える
9

再帰を気にしないのであれば Cirdec の回答が気に入っていますが、同等の折り畳みベースの回答はかなりきれいだと思います。

findM f = foldr test (return Nothing) 
    where test x m = do
              curr <- f x 
              case curr of
                  Just _  -> return curr
                  Nothing -> m

折り畳みをどれだけ理解しているかを試すちょっとしたテストです。

于 2013-09-01T03:38:25.317 に答える
6

これはそれを行う必要があります:

findM _ [] = return Nothing
findM filter (x:xs) =
    do
        match <- filter x
        case match of
             Nothing -> findM filter xs
             _ -> return match

どうしてもやりたいならポイント無料(編集で追加)

Alternative以下は、jozefgの回答のように折り畳みを使用して、ファンクターを使用してリスト内の何かを見つけます

findA :: (Alternative f) => (a -> f b) -> [a] -> f b
findA = flip foldr empty . ((<|>) .)

(Monad m) => m . Maybeのインスタンスを作成できるとは思いませんがAlternative、既存の関数があるふりをすることはできます。

-- Left biased choice
(<||>) :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
(<||>) left right = left >>= fromMaybe right . fmap (return . Just)
-- Or its hideous points-free version
(<||>) = flip ((.) . (>>=)) (flip ((.) . ($) . fromMaybe) (fmap (return . Just)))

findM次に、次のように同じように定義できますfindA

findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM = flip foldr (return Nothing) . ((<||>) .)
于 2013-09-01T03:07:25.823 に答える
5

MaybeTこれは、モナド変換子 と でかなりうまく表現できますData.Foldable

import Data.Foldable (msum)
import Control.Monad.Trans.Maybe (MaybeT(..))

findM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM f = runMaybeT . msum . map (MaybeT . f)

MaybeTスタックを生成するように検索関数を変更すると、さらに便利になります。

findM' :: Monad m => (a -> MaybeT m b) -> [a] -> MaybeT m b
findM' f = msum . map f

またはポイントフリーで:

findM' = (.) msum . map

元のバージョンも完全にポイントフリーにすることができますが、かなり読みにくくなります:

findM = (.) runMaybeT . (.) msum . map . (.) MaybeT
于 2013-09-01T08:21:48.190 に答える