2017-02-11 3 views
3

無限リスト(例えば、take 5、など)で動作する関数f :: [a] -> bがあります。厳密なモナドアクション(たとえばrandomIO)によって生成された値をこの関数に渡したいと思います。 this questionから厳密なモナドを持つ無限リストの操作

が、私はrepeatsequenceトリックアプローチはショー以下の例のように、厳格なモナドのために動作しないことを学んだ:だから

import Control.Monad.Identity 

take 5 <$> sequence (repeat $ return 1) :: Identity [Int] 
-- returns `Identity [1,1,1,1,1]` 
-- works because Identity is non-strict 

take 5 <$> sequence (repeat $ return 1) :: IO [Int] 
-- returns `*** Exception: stack overflow` 
-- does not work because IO is strict 

、その代わり、私の中に」機能を使用して考えました"モナドの文脈。私はこのcircular programming exampleに触発さと試みた:

let loop = do 
     x <- return 1 
     (_, xs) <- loop 
     return (take 5 xs, x:xs) 
in fst loop :: Identity [Int] 
-- Overflows the stack 

import Control.Monad.Fix 

fst <$> mfix (\(_, xs) -> do 
    x <- return 1 
    return (take 5 xs, x:xs)) :: Identity [Int] 
-- Overflows the stack 

とさえ

{-# LANGUAGE RecursiveDo #-} 
import System.Random 

loop' = mdo 
    (xs', xs) <- loop xs 
    return xs' 
where loop xs = do 
     x <- randomIO 
     return (take 5 xs, x:xs) 

print $ loop' 
-- Returns a list of 5 identical values 

しかし、これらの作品のどれを。私もどちらかでもIdentityの場合には動作しませんでしたConduitアプローチを試してみました:

import Conduit 

runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5 

したがって、私は知っているしたいと思います:

  1. 「円形」のどれもが仕事の上に接近しないのはなぜ?

  2. これには、unsafeInterleaveIOを含まない解決策が存在する場合。 (多分iteratee秒、Arrow秒?)

+3

一般的に、これは厳しい問題です。特に乱数の場合、簡単な方法があります: 'randoms <$> newStdGen :: Random a => IO [a]'はあなたが望むもの(無作為)の無限ランダムリストです。 – Alec

+0

@Alecコメントをいただきありがとうございます。ここでは例を簡単にするために 'randomIO'を使用しています。実際には、ソケット経由で受信したメッセージを処理したいと思います。 –

+0

ソケットから受信したメッセージの無限リストのようなものですか? – Alec

答えて

4

私は例を簡単にするために、ここでrandomIOを使用しています。実際には、私はソケット

unsafeInterleaveIOなしでは不可能であることを

介して受信したメッセージを処理したいと思います。その日の終わりの問題は、IOアクションのシーケンスを取らなければならないということです。参照値が透明な値の評価順序は重要ではありませんが、IOアクションの評価順序は問題ありません。ソケットを介して受信したすべてのメッセージの怠惰な無限リストを望むなら、IOアクションのシーケンスがどこにあるのかを先験的に知らせる必要があります(unsafeInterleaveIOを使用しない限り)。あなたが求めている

Arrow抽象化はArrowLoopと呼ばれるが、それはあまりにも厳しいモナドのための右締め法に問題を持っています。

は、一見、それは(mdo又はmfixを介して現れる)ように見えることができるソリューションもあるが、単にArrowLoopからloopように、ビット深いshows that fixIO has problemsを掘ります。

しかし、ときどきIOアクションが次々と実行されるという制限は少し過度であり、それはunsafeInterleaveIOのためです。docs

unsafeInterleaveIOを引用するIO計算を遅延延期することを可能にします。 IO aの値が渡された場合、IOは、aの値が要求された場合にのみ実行されます。今


、あなたが明示的に私はうまくいけばそれが進むべき道であるあなたを説得するために管理しているとして、あなたは、unsafeInterleaveIO解決を望んでいなかったことを言っていても、ここにある:

ここ
interweavingRepeatM :: IO a -> IO [a] 
interweavingRepeatM action = unsafeInterleaveIO ((:) <$> action <*> interweavingRepeatM action) 

とは、乱数のために働いているされています

ghci> import System.Random 
ghci> sourceOfRandomness <- interweavingRepeatM randomIO :: IO [Integer] 
ghci> take 10 sourceOfRandomness 
[-2002742716261662204,7803971943047671004,-8395318556488893887,-7372674153585794391,5906750628663631621,6428130029392850107,6453903217221537923,-8966011929671667536,6419977320189968675,-1842456468700051776] 
+0

この説明にはおかげさまですが、もっと詳しく説明できます:1. IOから変換された一般的なモナドについてこの問題に対処する方法2. mdoのアプローチが繰り返し行われたのはなぜですか?値は? –

+1

@DouglasVieira 1.私はあなたが一般的にできるとは思わない - モナドはシーケンシングの性質をコード化することを意図している2. 'fixIO'と' fixIO'を使う 'mdo'デザイナーは、 nce_([source](https://hackage.haskell.org/package/base-4.9.1.0/docs/src/System.IO.html#fixIO)を参考にしてください)。本当にリストを返すべきであったものを書いていたとしても、もっと混乱している「MVar」関連のエラーメッセージ(あるいは悪化しているプログラム)に対して頭が痛いでしょう。 – Alec

+0

@DouglasVieira 1.に関して - あなたがリンクしたパッケージが 'IO'関数も公開していることがわかります。それらを使用して 'unsafeInterleaveIO'を使ってリストを取得し、' liftIO'を目的のモナドに戻すことができます。 – Alec

3

Alec's answerをyをカバー私たちの一般的な質問。以下は、具体的には約コンジットおよび類似のストリーミングライブラリである。

また、私はどちらかでもIdentityの場合には動作しませんでしたConduitアプローチを試してみました:

import Conduit 

runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5 

ストリーミングライブラリは、一般的に言及困難あなたの並べ替えを避けるために使用されているが(参照: Pipes.Tutorialの開かれた発言)、リストの代わりにストリームタイプを使用することを前提としています。 Conduitドキュメントで説明される方法sinkList、例えば、考えてみましょう:

は、ストリームからすべての値を消費し、リストとして返します。これにより、すべての値がメモリに取り込まれます。 yieldMany直後sinkManyを使用することを意味振り出しに戻っをもたらします

:メモリにすべての値をもたらすがsequenceIOと無限リスト使用不可能の組み合わせを作る正確に何です。代わりに、ストリーミングライブラリのインフラストラクチャを使用してパイプラインのステージを構築する必要があります。 - 最初に私はrepeatMC randomIOで自分を巻いていた

GHCi> import Conduit 
GHCi> runConduitPure $ yieldMany [1..] .| takeC 5 .| sinkList 
[1,2,3,4,5] 
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| printC -- try it without takeC 
1 
2 
3 
4 
5 
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| scanlC (+) 0 .| printC 
0 
1 
3 
6 
10 
15 
GHCi> :{ 
GHCi| runConduit $ yieldMany [1..] .| takeC 5 
GHCi|  .| awaitForever (\x -> liftIO (print (2*x)) >> yield x) 
GHCi|  .| printC 
GHCi| :} 
2 
1 
4 
2 
6 
3 
8 
4 
10 
5 
GHCi> runConduit $ (sourceRandom :: Producer IO Int) .| takeC 5 .| printC 
1652736016140975126 
5518223062916052424 
-1236337270682979278 
8079753510915129274 
-609160753105692151 

(私はsourceRandomに気づく作るためのおかげでMichael:ここコンジットコンジット・コンビネータから主に既製のものを使用していくつかの簡単な例があります。 )

+0

Conduitについての説明をありがとう。しかし問題は、(無限の)リスト上で実際に動作する任意の関数f :: [a] - > bで解決するソリューションがほしいということです。したがって、あなたが指摘したように、「コンジット」は私が探している解決策ではありません。 @Alecによって指摘されているように、さらに悪いことに、一般的な場合の解決策はありません。より多くの文脈を与えるために、私はFunctional Reactive Programmingの単純な実装を試みていました。ここで、プリミティブ型 'Event :: Ord t => [(t、a)]'は時間順に並べられた無限リストです'f :: Event-> b'が必要です)。 –

+0

あなたが実際に言及している関数fは、 "(例えば、take 5、takeWhile(<100)、scanl(+)0など)"は、すべてが本当に無限のストリームで動作する基本ストリーム変換です。あなたはストリームではなく無限リストを使用している理由を与えていない。これは 'sequence'とcoを必要とせずに' Data.List'のほとんどのAPIをサポートしています。そのシーケンスは、「一般的なケースでは」全面的に救済することはできません。 – Michael

関連する問題