Fulkerson
Fulkerson

Reputation: 128

Lazily read and manipulate float from stdin in haskell

I'm trying to read data as Doubles from stdin, manipulate them and write them as well. What I've come up with so far is:

import qualified Data.ByteString.Lazy as B
import Data.Binary.IEEE754
import Data.Binary.Get

-- gives a list of doubles read from stdin
listOfFloat64le = do
  empty <- isEmpty
  if empty
     then return []
     else do v <- getFloat64le
             rest <- listOfFloat64le
             return (v : rest)


-- delay signal by one
delay us = 0 : us

-- feedback system, add delayed version of signal to signal
sys us = zipWith (+) us (delay us)

main = do
    input <- B.getContents
    let hs = sys $ runGet listOfFloat64le input
    print $ take 10 hs

The idea is to fead data to the program which is then passed through a feedback system before it is written to stdout. Although right now it just prints the first 10 values.

This works but does not seem to evaluate lazily. I.e it has to read all the input into memory. So:

dd if=/dev/urandom bs=8 count=10 | runhaskell feedback.hs

will work just fine but:

dd if=/dev/urandom | runhaskell feedback.hs

will not. My guess is it's the listOfFloat64le function that makes things not work properly. So how do I create an iterable to pass into my sys function without having to read everything into memory?

I'm not a very experienced haskeller.

Upvotes: 4

Views: 402

Answers (3)

Satvik
Satvik

Reputation: 11218

This seems a standards problem where you can easily use something like pipes or conduits. You can make stdin as the source and stdout as the sink and apply the transformer as a conduit.

Upvotes: 0

Fulkerson
Fulkerson

Reputation: 128

I took another route by instead splitting the ByteString at intervals of 8 bytes and mapping over it instead:

import qualified Data.ByteString.Lazy as L
import Data.Binary.IEEE754
import Data.Binary.Get

-- delay signal by one
delay us = 0 : us

-- feedback system, add delayed version of signal to signal
sys us = zipWith (+) us (delay us)

-- split ByteString into chunks of size n
chunk n xs = if (L.null xs)
        then []
        else y1 : chunk n y2
          where
            (y1, y2) = L.splitAt n xs


main = do
    input <- L.getContents

    let signal = map (runGet getFloat64le) (chunk 8 input)
    print $ take 10 (sys signal)

This seem to work atleast but I don't know what the performance is like.

EDIT: I switched from chunk to chunker which uses runGetState instead:

chunker :: Get a -> L.ByteString -> [a]
chunker f input = if (L.null input)
                     then []
                     else val : chunker f rest
                       where
                        (val, rest, _) = runGetState f input 0

And using it like: let signal = chunker getFloat64le input

Upvotes: 1

ony
ony

Reputation: 13253

See this question. Looks like Binary become more strict than it was long time ago when I used it.

Upvotes: 0

Related Questions