12

Haskell から呼び出す C で記述された関数がいくつかあります。これらの関数は を返しIO (CInt)ます。戻り値に関係なく、すべての関数を実行したい場合がありますが、これは簡単です。サンプルコードのために、これは現在何が起こっているかの一般的な考えです:

Prelude> let f x = print x >> return x
Prelude> mapM_ f [0..5]
0
1
2
3
4
5
Prelude>

望む副作用が得られ、結果は気にしません。しかし、ここでは、目的の結果を返さない最初の項目の直後に実行を停止する必要があります。戻り値が 4 以上の場合、実行を停止する必要があるとしましょう。その場合、私がやりたいことは次のとおりです。

Prelude> takeWhile (<4) $ mapM f [0..5]

これは私にこのエラーを与えます:

<インタラクティブ>:1:22:
    予想される型 `[b]' と推測された型 `IO a' を一致させることができませんでした
    `mapM' の最初の引数、つまり `f'
    `($)' の 2 番目の引数、つまり `mapM f ([0 .. 5])'
    式: takeWhile (< 4) $ mapM f ([0 .. 5])

そして、それは私には理にかなっています-結果はまだ IO モナドに含まれており、IO モナドに含まれている 2 つの値を単純に比較することはできません。これがまさにモナドの目的であることを知っています-結果を連鎖させ、特定の条件が満たされたときに操作を破棄します-しかし、この場合、IOモナドを「ラップ」して、条件でチェーンの実行を停止する簡単な方法はありますか?のインスタンスを記述せずに、私の選択のMonadPlus?

ftakeWhileの目的で、 から値を「持ち上げる」ことはできますか?

これは、ファンクターが適合するソリューションですか? ファンクターはまだ「クリック」していませんが、これはファンクターを使用するのに良い状況かもしれないという印象を持っています。


アップデート:

@sth は私が望むものに最も近い答えを持っています - 実際、それは私が目指していたものとほぼ同じですが、明示的に再帰的でない標準的な解決策があるかどうかを知りたいです - これは Haskell です。全て!質問の言い方を振り返ってみると、自分が望んでいる行動について十分に明確ではなかったことがわかります。

上記fの例で使用した関数は、単なる例です。実際の関数は C で記述されており、その副作用のみに使用されます。mapM_ f (takeWhile (<4) [0..5])実行されるまで、入力が実際に成功するか失敗するかがわからないため、@Tomの提案を使用できません。

返されたリストも実際には気にしません。リストが使い果たされるか、最初の C 関数が失敗コードを返すまで、C 関数を呼び出したいだけです。

C スタイルの擬似コードでは、私の動作は次のようになります。

do {
    result = function_with_side_effects(input_list[index++]);
} while (result == success && index < max_index);

繰り返しますが、@ sthの回答は、結果が破棄される可能性がある(すべきか?)ことを除いて、私が望む正確な動作を実行します。関数は私のdropWhileM_目的と同等です。なぜそのような関数やtakeWhileM_Control.Monad にないのですか? メーリング リストで同様の議論があったようですが、それについては何も語られていないようです。

4

5 に答える 5

16

シーケンスを次のように定義できます

sequence xs = foldr (liftM2 (:)) (return []) xs

liftM2あなたが見てきた問題は、止める機会m2がないことlaunchTheMissilesです。

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
    x1 <- m1
    x2 <- m2
    return (f x1 x2)

guard次のように使用すると魅力的です。

sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs
  where myLiftM2 p f m1 m2 = do
            x1 <- m1
            guard $ p x1
            x2 <- m2
            return (f x1 x2)

IO モナドはMonadPlusのインスタンスではないため、上記のコードはアプリケーションで失敗します。

だからその手をもう少し握って

module Main where

import Control.Monad

printx :: Int -> IO Int
printx x = do
    print x
    return x

sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs
  where myLiftM2 f z m1 m2 = do
            x1 <- m1
            if p x1 then do x2 <- m2
                            return $ f x1 x2
                    else return z

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..10]
  ys <- sequenceUntil (< 4) as
  print ys

asは 1 から 10 までのアクションのリストですが、出力は

1
2
3
4
[1,2,3]

結果を破棄するのは簡単です。

sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceUntil_ p xs = sequenceUntil p xs >> return ()

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..]
  sequenceUntil_ (< 4) as

[1..]新しいコンビネータが laziness を維持していることを示すの使用に注意してください。


あなたは好むかもしれませんspanM:

spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a])
spanM _ [] = return ([], [])
spanM p (a:as) = do
  x <- a
  if p x then do (xs,bs) <- spanM p as
                 return (x:xs, bs)
         else return ([x], as)

結果リストに失敗した要素が含まれているという点で、 spanとは少し異なることに注意してください。ペアの 2 番目は残りのアクションです。例えば:

*Main> (xs,bs) <- spanM (< 4) as
1
2
3
4
*Main> xs  
[1,2,3,4]
*Main> sequence bs
5
6
7
8
9
10
[5,6,7,8,9,10]

さらに別の選択肢:

untilM :: Monad m => (a -> Bool) -> [m a] -> m ()
untilM p (x:xs) = do
  y <- x
  unless (p y) $ untilM p xs

述語の意味が補完されていることに注意してください。

*Main> untilM (>= 4) as
1
2
3
4
于 2009-07-16T14:47:37.560 に答える
14

標準ライブラリにはaのようなものはないと思いますがtakeWhileM、必要なだけのIOが実行されるように、自分で作成することもできます。

takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
takeWhileM _ [] = return []
takeWhileM p (a:as) =
   do v <- a
      if p v
         then do vs <- takeWhileM p as
                 return (v:vs)
         else return []

提供されたリストは、述語と一致しない要素が見つかるまでのみ評価されます。

*Main> takeWhileM (<4) (map f [1..5])
1
2
3
4
[1,2,3]
于 2009-07-15T21:32:41.690 に答える
12

編集:探しているものがわかりました。

gbacon は、sequenceWhile必要なほとんど「プリミティブ」である素敵な関数を投稿しました。

実際には、副作用だけに関心があるので、sequenceWhile_十分なはずです。定義は次のとおりです (これも gbacon に触発されて、彼に投票してください!):

sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my)
                            (return ()) xs

これを次のように呼び出します。

Prelude Control.Monad> sequenceWhile (<4) $ map f [1..]

元の答え:

IOで使用するためにモナドから値を単に「持ち上げる」ことはできませんが、モナド内で使用するためにtakeWile「持ち上げる」ことはできますtakeWhile!

liftM(a -> b)関数は関数をfunction に取ります。(m a -> m b)ここmで、 はモナドです。

(補足として、 Hoogleで型を検索すると、このような関数を見つけることができます。この場合は、次を検索します。Monad m => (a -> b) -> (m a -> m b))

これliftMを行うことができます:

Prelude> :m + Control.Monad
Prelude Control.Monad> let f x = print x >> return x
Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5]
0
1
2
3
4
5
[0,1,2,3]

さて、これはあなたが望んでいたものではないかもしれません。は、リストを返す前に、関数をリスト全体に順番にmapM適用します。fその結果のリストは、リフトされた関数に渡されtakeWhileます。

3 番目の要素の後で印刷を停止する場合は、print の呼び出しを停止する必要があります。つまり、そのfような要素には適用しないでください。したがって、次のような単純なものになります。

Prelude> mapM_ f (takeWhile (<4) [0..5])

ところで、リストを返す前に、最初にすべてを出力するのはなぜだろうか。 mapMこれは、関数をその定義に置き換えることで確認できます。

mapM f [0..1]
=
sequence (map f [0..1])
=
sequence (f 0 : map f [1..1])
=
sequence (f 0 : f 1 : [])
=
sequence ((print 0 >> return 0) : f 1 : [])
= 
sequence ((print 0 >> return 0) : (print 1 >> return 1) : [])
=
do x  <- (print 0 >> return 0)
   xs <- (sequence ((print 1 >> return 1) : []))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- sequence ([])
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- return []
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y <- (print 1 >> return 1)
             return (y:[]))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (print 1 >> return (1:[]))
   return (x:xs)
=
do x <- (print 0 >> return 0)
   print 1
   return (x:1:[])
=
do print 0
   print 1
   return (0:1:[])

関数をその定義に置き換えるこのプロセスは、等式推論と呼ばれます。

私が間違いを犯していなければ、(うまくいけば) mapM(を使用してsequence) 最初にすべてを出力し、次にリストを返すことがわかります。

于 2009-07-15T21:11:25.383 に答える
6

「リスト」パッケージのものが使用できます。

import Control.Monad.ListT (ListT)
import Data.List.Class (execute, fromList, joinM, takeWhile)
import Prelude hiding (takeWhile)

f x = print x >> return x
main =
  execute . takeWhile (< 4) .
  joinM $ fmap f (fromList [0..5] :: ListT IO Int)
  • fromList [0..5]モナドアクションを実行しない 0..5 を含むモナドリストを作成します
  • fmap fそのリストへの結果は、ListT IO (IO Int)まだモナド アクションを実行せず、1 だけを含む になります。
  • joinMに変換しListT IO Intます。アイテムが消費されると、含まれているすべてのアクションが実行され、その結果がリストの値になります。
  • takeWhileは任意の に対して一般化されますList[]と " " の両方Monad m => ListT mが のインスタンスですList
  • executeモナド リストを消費し、そのすべてのアクションを実行します。
  • 結果に興味がある場合は、使用できます"toList :: List m => m a -> ItemM m [a]"(" ItemM (ListT IO)" はIO)。したがって、この場合は " toList :: ListT IO a -> IO [a]" です。さらに良いことに、 などの高階関数を使用し続けてscanl、実行中のモナド リストを処理できます。
于 2009-07-15T23:35:22.097 に答える
3

最近では、takeWhileM、dropWhileM、deleteByM などの便利な関数を含むMonadListハックを使用できます。

于 2012-03-15T18:32:48.167 に答える