I am new to both purescript and haskell (and even javascript and node), so I am stumped in trying to figure out how to save off the output of a child process in purescript. I am using the purescript-node-childprocess and purescript-node-fs modules. Basically, the problem I am having is this:
import Node.ChildProcess (CHILD_PROCESS, SpawnOptions, defaultSpawnOptions, Exit(..), spawn, onExit, stdout)
import Node.Stream (onData)
type CPEffect = forall e
. Eff (cp :: CHILD_PROCESS
, console :: CONSOLE
, err :: EXCEPTION
, buffer :: BUFFER | e
) Unit
-- | Basically a wrapper around the spawn command.
-- | Takes a command, an array of arguments, and a record of
-- | options to pass to spawn.
launch :: String -> Array String -> SpawnOptions -> CPEffect
launch cmd args opts = do
cmd' <- spawn cmd args opts
onExit cmd' defaultExitHdlr
-- My problem is with onData due to its return type
onData (stdout cmd') -- what do I put as the callback handler arg?
log $ "done with " <> cmd
As shown in my comment above, the problem is the onData function which comes from the Node.Stream module. The problem is in the 2nd arg and return type:
onData :: forall w eff
. Readable w (err :: EXCEPTION | eff)
-> (Buffer -> Eff (err :: EXCEPTION | eff) Unit)
-> Eff (err :: EXCEPTION | eff) Unit
Since the return is an Eff which returns Unit, how can I save off the child process output? The 2nd argument is a function which takes a Buffer and also returns the same type. Indeed, it is this function which is getting the data from the Readable (which is the stdout of the childprocess). In other words, the first argument is the stdout stream from node of the child process, and the 2nd arg is a callback handler which will fill in the buffer from the stdout stream.
But since callback handler returns Unit, I don't see how I can accumulate the output from the child process. I am still in the process of learning Monad Transformers, so is this a solution? Can I create a Writer monad that somehow wraps this?
If I was going to do this in purescript without using the FFI, I would use a Ref, and in the onData callback, read the ref value, make a new string by appending the new data to the ref value, and then update the ref value with this new string.
WriterT is indeed useful for this kind of thing although I probably would not use it in this particular case, because it could be a bit awkward to work out how to get the types to line up, since the callback takes Eff, not WriterT something Eff.
Another option, which I used to solve this exact issue in Pulp, is to use the npm package
concat-stream
. In fact you might well find Pulp useful as a real world example of using these libraries - I think the modules Pulp.Exec and Pulp.System.Stream are the ones you will be interested in.