I'm learning about the Writer monad right now, and I'm not sure if it's correct to want to read both the value and the accumulator of the monad within a do
block. For example, in the coltzSeq function below I want to read the length of the Array accumulator as the final computation of the function. Is it possible to do this or this an incorrect use of Writer? Obviously, I could let the caller read the length of the final array, or I could use the State monad, but this is just an exercise for me.
module Main where
import Prelude
import Data.Tuple
import Control.Monad.Writer
import Math (remainder, (%))
import Data.Int (toNumber, fromNumber)
import Control.Monad.Eff.Console (logShow)
coltz :: Number -> Number
coltz n = case (n % 2.0 == 0.0) of
true -> n / 2.0
false -> 3.0 * n + 1.0
coltzW :: Number -> Writer (Array Number) Number
coltzW n = do
tell [n]
pure $ coltz n
-- Computes a coltz sequence and counts how many
-- steps it took to compute
coltzSeq :: Number -> Writer (Array Number) Int
coltzSeq n = do
-- Can read the value in the writer
-- but not the writer's internal state
v <- (coltzW n)
let l = 1
-- Can read the value and the internal
-- state, but it's not bound to the monad's context.
-- let a = runWriter (coltzW n)
-- let v = fst a
-- let l = length (snd a)
case (v) of
1.0 -> pure $ l
_ -> to1 v
Edit:
I tried gb.'s suggestion and tried using the listens
function which has type (Monoid w, Monad m) => forall w m a b. MonadWriter w m => (w -> b) -> m a -> m (Tuple a b)
. If we use id
in this context the type would be...
MonadWriter w m => (w -> b) -> m a -> m (Tuple a b)
w = Array Number
m = WriterT (Array Number) Identity (alias: Writer (Array Number) )
b = Array Number
a = Number
(Array Number -> Array Number) ->
Writer (Array Number) Number ->
Writer (Array Number) (Tuple Number (Array Number))
So listens id
accepts Writer (Array Number) Number)
, and returns a Writer with the value being the current Writer state (since we used id
). However, I keep getting type errors with all the ways I try and use listens
to1 :: Number -> (Writer (Array Number)) Int
to1 n = do
v <- (coltzW n)
-- a <- snd <$> listens id
-- let l = snd <$> (listens id (execWriter (coltzW n)))
-- let l = execWriter (listens id (coltzW n))
-- Seems like this one should work to get Array Number
-- let l = snd <$> (listens id (coltzW n))
case (v) of
1.0 -> pure 1
_ -> to1 v
Edit2:
I figured out what I needed to do. For some reason I needed to add a type annotation when using listens
.
lengthOfSeq :: Writer (Array Number) Int -> Writer (Array Number) Int
lengthOfSeq c = do
-- Without type annotation, I get this error...
-- No type class instance was found for
--
-- Control.Monad.Writer.Class.MonadWriter (Array t0)
-- (WriterT (Array Number) Identity)
--
-- The instance head contains unknown type variables. Consider adding a type annotation.
Tuple a w <- (listens id c :: Writer (Array Number) (Tuple Int (Array Number)))
pure $ length w
to1 :: Number -> (Writer (Array Number)) Int
to1 n = lengthOfSeq $ seq n
where
seq n = do
v <- coltzW n
case (v) of
1.0 -> do
pure 1
_ -> seq v