Reading from the Standard Input (stdin) using PureScript

dgopsq

Diego Pasquali

Posted on December 30, 2021

Reading from the Standard Input (stdin) using PureScript

These last few weeks I started learning PureScript again. I love this language, it’s the quintessence of functional programming, but it lacks in community and libraries compared for example to TypeScript, and this sadly makes it really difficult for me to use in my side projects.

While I was doing some challenges online, I had the need to retrieve an input string from stdin. I found out that this case is not really well covered anywhere, so why not writing a new blog post? 🙃

First of all, to read from the Standard Input (stdin) the library purescript-node-process is needed. This library exposes stdin which is a Readable (a simple readable stream) and it will be the key component around which we’ll create our function. Since we are dealing with a simple stream, what we want to create is a function that takes that same stream and return a new string:

import Prelude
import Control.Monad.ST.Class (liftST)
import Control.Monad.ST.Ref as STRef
import Data.Either (Either(..))
import Effect (Effect)
import Effect.Aff (effectCanceler, launchAff_, makeAff)
import Effect.Aff.Class (class MonadAff, liftAff)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Node.Encoding (Encoding(..))
import Node.Process (stdin)
import Node.Stream (Readable, destroy, onDataString, onEnd, onError)

-- Accumulate a readable stream inside a string.
streamAccumulator :: forall m r. MonadAff m => Readable r -> m String
streamAccumulator r =
  liftAff <<< makeAff
    $ \res -> do
        -- Create a mutable reference using `purescript-st`.
        inputRef <- liftST $ STRef.new ""

        -- Handle the `onDataString` event using
        -- the UTF8 encoding.
        onDataString r UTF8 \chunk ->
          void <<< liftST $ STRef.modify (_ <> chunk) inputRef

        -- Handle the `onEnd` event.
        onEnd r do
          input <- liftST $ STRef.read inputRef
          res $ Right input

        -- Handle the `onError` event.
        onError r $ Left >>> res

        -- Return a `Canceler` effect that will
        -- destroy the stream.
        pure $ effectCanceler (destroy r)

-- Execute the program.
main :: Effect Unit
main =
  launchAff_ do
    input <- streamAccumulator stdin
    liftEffect $ log input
Enter fullscreen mode Exit fullscreen mode

This looks a bit messy but it’s actually simple. Since the process of handling a stream is “event-driven” we are going to use a safe mutable string (from purescript-st) to accumulate our input every time the onDataString event will be triggered. This whole process is asynchronous, meaning that we have to wait for the onEnd event (or onError if something bad happened) to actually return the accumulated string. The effect monad Aff solves this through makeAff. The res callback has to be called with an Either parameter, and only when this callback will be triggered the program can continue its execution.

Peace ✌️

💖 💪 🙅 🚩
dgopsq
Diego Pasquali

Posted on December 30, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related