PureScript by Example - Exercise Clarification (Monad Transformer Stacks)

#1

At the end of Monad Transformer Stacks section of the Monadic Adventures chapter in PureScript by Example there’s an exercise that builds on a previous one, however, I don’t get how a possible solution should work. Here’s the exercise’s text.

  1. (Difficult) Use the ReaderT and WriterT monad transformers to reimplement the document printing library which we wrote earlier using the Reader monad. Instead of using line to emit strings and cat to concatenate strings, use the Array String monoid with the WriterT monad transformer, and tell to append a line to the result.

For me this tells that I should rework the previous document printing solution (I have one similar to this) in a way that I don’t use the line and cat functions and somehow I should use the tell function instead of these, but I’m not sure how is this possible.

An example of how using this new solution might go would be useful, along the lines of this example from a previous section.

 render
    $ cat
        [ line "Here is some indented text:"
        , indent
            $ cat
                [ line "I am indented"
                , line "So am I"
                , indent $ line "I am even more indented"
                ]
        ]

Any insight is gladly appreciated.

Mentioning @paf31 in hope of first-hand insight.

#2

I think the goal would be to write something like:

render do
  line "Here is some indented text:"
  indent do
    line "I am indented"
    line "So am I"
    indent do
      line "I am even more intented"

Basically you don’t see cat anymore explicitly. I kept line, but it’s tell <<< Array.singleton. Otherwise you have to write

render do
  tell [ "Here is some indented text:" ]
  indent do
    tell [ "I am indented" ]
    tell [ "So am I" ]
    indent do
      tell [ "I am even more intented" ]
#3

Hmm, I came to the same conclusion, but what bugs me is that if we omit the line function or if it’s simply tell <<< Array.singleton then how each line will get indented? I couldn’t figure this part out … yet.

My guess is that you did (did you?) and if that’s the case it helps me to see that an example like yours above could work.

#4

I would look at the MonadWriter interface. tell is part of MonadTell, which isn’t enough to complete this task. However MonadWriter has operations for observing and manipulating the accumulator.

https://pursuit.purescript.org/packages/purescript-transformers/4.2.0/docs/Control.Monad.Writer.Class#t:MonadWriter

#5

Ah, thanks for the insight. The book doesn’t mention this, I assumed what was covered by the section at hand would be enough.

Edit:

I was able to use censor to achieve what was needed, although my interpretation of Level was rather something like a tab-shift width. I didn’t need to use local this way tho’.

import Prelude
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.Writer (WriterT, censor, execWriterT, tell)
import Data.Array (replicate)
import Data.Identity (Identity)
import Data.Newtype (unwrap)
import Data.String (joinWith)
import Data.String.CodeUnits (fromCharArray)

type Level
  = Int

type Doc
  = WriterT (Array String) (ReaderT Level Identity) Unit

indent :: Doc -> Doc
indent d = do
  level <- ask
  censor (map (append (fromCharArray (replicate level ' ')))) d

render :: Doc -> String
render d = joinWith "\n" (unwrap (runReaderT (execWriterT d) 2))