Reputation: 934
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
Upvotes: 0
Views: 157
Reputation:
You cannot access the current accumulator. Writer
is write-only. Use State
instead of Writer
if you need the current accumulator. listen
can only give you the accumulator of a finished computation.
Upvotes: 0
Reputation: 4649
I think listens
is the function you're looking for: https://pursuit.purescript.org/packages/purescript-transformers/1.0.0/docs/Control.Monad.Writer#v:listens, if I understand correctly what it is you're trying to do.
You can pass id
in if you're not interested in transforming the value, and if you only want to get hold of the accumulated value then a <- snd <$> listens id
should do the trick.
Upvotes: 1