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.
(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"
]
]
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.
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.
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))
I think that in the original exercise Level in the Reader Monad was used to indicate how deep current line is, not how much each line should be indented.
I’m sorry for reviving an old thread, but I was stuck with this task and your advise helped me a lot, so maybe someone will find this useful too
Here’s my approach:
import Prelude
import Control.Monad.Reader (ReaderT, ask, local, runReaderT)
import Control.Monad.Writer (WriterT, execWriterT, tell)
import Control.Monad.Trans.Class (lift)
import Data.String (joinWith)
import Data.Identity (Identity)
import Data.Newtype (unwrap)
import Data.Monoid (power)
type Level = Int
type Doc = (WriterT (Array String) (ReaderT Level Identity)) Unit
line :: String -> Doc
line s = do
level <- lift $ ask
tell [ (power " " level) <> s ]
pure unit
indent :: Doc -> Doc
indent = local ((+) 1)
render :: Doc -> String
render doc = joinWith "\n" $ unwrap $ runReaderT (execWriterT doc) 0
and the usage:
suite "indents" do
let
expectedText =
"Here is some indented text:\n\
\ I am indented\n\
\ So am I\n\
\ I am even more indented"
test "should render with indentations" do
Assert.equal expectedText
$ 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 indented"