I have this bit of code:
import Data.Random
import Control.Monad.State
foo :: s -> StateT s RVar ()
foo s = do
p <- lift $ (uniform 0 1 :: RVar Double)
if p > 0.5 then put s else return ()
And I would like to refactor its signature to be of form:
foo :: (MonadState s m, RandomSource m s) => s -> m ()
I thought I could equip RVar with MonadState functions:
{- LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}
instance MonadState s m => MonadState s (RVarT m) where
get = lift get
put = lift . put
state = lift . state
and write:
foo :: (MonadState s m, RandomSource m s) => s -> m ()
foo s = do
p <- (uniform 0 1 :: RVar Double)
if p > 0.5 then put s else return ()
But I am getting this inexplicable error:
Couldn't match type ‘m’
with ‘t0 (RVarT Data.Functor.Identity.Identity)’
‘m’ is a rigid type variable bound by
the type signature for
foo :: (MonadState s m, RandomSource m s) => s -> m ()
at ApproxMedian.hs:99:8
Expected type: m Double
Actual type: t0 (RVarT Data.Functor.Identity.Identity) Double
Relevant bindings include
foo :: s -> m () (bound at ApproxMedian.hs:100:1)
In a stmt of a 'do' block: p <- lift $ (uniform 0 1 :: RVar Double)
In the expression:
do { p <- lift $ (uniform 0 1 :: RVar Double);
if p > 0.5 then put s else return () }
In an equation for ‘foo’:
foo s
= do { p <- lift $ (uniform 0 1 :: RVar Double);
if p > 0.5 then put s else return () }
Failed, modules loaded: Core, Statistics.
Please explain the error and help make the more generic signature possible?
If I wanted to do:
foo :: (MonadRandom m, MonadState s m) => s -> m ()
How would I implement it? I cannot use uniform any more. Because it locks me to signature RVar a but I really want MonadRandom m => m a,
or at the very least Monad m => RVarT m a
uniform is not polymorphic in the monad it runs in (in other words, you can't run it in any choice of m if all you know is that RandomSource m s):
uniform :: Distribution Uniform a => a -> a -> RVar a
However, if you have a source of entropy, you can runRVar it in any m if RandomSource m s:
runRVar :: RandomSource m s => RVar a -> s -> m a
which means you can write foo with your desired type signature as
foo :: (MonadState s m, RandomSource m s) => s -> m ()
foo s = do
p <- runRVar (uniform 0 1 :: RVar Double) s
when (p > 0.5) $ put s
Related
Why ghc doesn't complain about types being rigid in the following function?
play :: (Monad m, MonadIO m, Random a) => a -> a -> m a
play r1 r2 = do
randomRIO (r1, r2)
In the above case, the m is actually IO. This code compiles. While othertimes, when I use a concrete type in a generic function, ghc complains that I am using a concrete type. Do I miss something here?
For example, if I say:
play :: (Monad m, MonadIO m, Random a) => a -> a -> m a
play r1 r2 = do
randomRIO (1, 6::Int)
This time ghc complains that I'm using a rigid type. The thing that bothers me is that IO is in a way a concrete or rigid type because it's IO and not Maybe.
Edit 1:
Complete code that compiles without error:
{-# LANGUAGE ConstraintKinds #-}
module Main where
-- showing how to program in the mtl style
import Control.Monad.IO.Class
import System.Random
type Game m a = (Monad m, MonadIO m, Random a, Show a)
-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
yell "hi"
randomRIO (r1, r2)
--rollDice r1 r2
-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str
-- step 3
rollDice :: (Game m a) => a -> a -> m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)
main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
-- play 1 (6::Int) >>= print
-- play 'a' 'z' >>= print
-- putStrLn "done"
Code that doesn't compile:
{-# LANGUAGE ConstraintKinds #-}
module Main where
-- showing how to program in the mtl style
import Control.Monad.IO.Class
import System.Random
type Game m a = (Monad m, MonadIO m, Random a, Show a)
-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
yell "hi"
randomIO 'a' 'z'
--rollDice r1 r2
-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str
-- step 3
rollDice :: (Game m a) => a -> a -> m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)
main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
-- play 1 (6::Int) >>= print
-- play 'a' 'z' >>= print
-- putStrLn "done"
This doesn't compile:
{-# LANGUAGE ConstraintKinds #-}
module Main where
-- showing how to program in the mtl style
import Control.Monad.IO.Class
import System.Random
type Game m a = (Monad m, MonadIO m, Random a, Show a)
-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
yell "hi"
**randomRIO (1, 6::Int)**
--rollDice r1 r2
-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str
-- step 3
rollDice :: (Game m a) => a -> a -> m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)
main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
-- play 1 (6::Int) >>= print
-- play 'a' 'z' >>= print
-- putStrLn "done"
Update
Just realized that my confusion comes from reading the wrong documentation of random
I was reading the random-1.1 documentation while using the random-1.2 in my code.
For people's reference:
random-1.1
randomRIO :: (a, a) -> IO a
random-1.2
randomRIO :: (Random a, MonadIO m) => (a, a) -> m a
I guess you are using random-1.2, which uses a more general type for randomRIO.
In that updated package, randomRIO is polymorphic both on the monad m and the value type a. Its type is:
randomRIO :: (Random a, MonadIO m) => (a, a) -> m a
So, when you write (renaming type variables for clarity)
play :: (Monad m1, MonadIO m1, Random a1) => a1 -> a1 -> m1 a1
play r1 r2 = do
randomRIO (r1, r2)
GHC infers m a ~ m1 a1, which implies m ~ m1 and a ~ a1, so randomRIO is called with those type parameters (m and a are not rigid here). Note that IO is not involved here at all: m could be IO, but could also be any other monad (in the MonadIO class).
Instead, when you write
play :: (Monad m1, MonadIO m1, Random a1) => a1 -> a1 -> m1 a1
play r1 r2 = do
randomRIO (1, 6::Int)
GHC infers once again m a ~ m1 a1, hence m ~ m1 and a ~ a1, but also infers a ~ Int since we are passing a pair of Ints to randomRIO. From a ~ a1 and a ~ Int we infer a1 ~ Int which triggers the type error since a1 is rigid.
If we instead had only a more specific type for randomRIO (such as the one we had prior to random-1.2)
randomRIO :: (Random a) => (a, a) -> IO a
then your reasoning would be correct: we would get m1 ~ IO during type inference, and that would cause an error since m1 is rigid. We don't get this since randomRIO is more general.
My question is quite similar to How to modify using a monadic function with lenses? The author asked if something like this exists
overM :: (Monad m) => Lens s t a b -> (a -> m b) -> s -> m t
The answer was mapMOf
mapMOf :: Profunctor p =>
Over p (WrappedMonad m) s t a b -> p a (m b) -> s -> m t
I'm trying to implement a function that modifies state in MonadState using a monadic function:
modifyingM :: MonadState s m => ASetter s s a b -> (a -> m b) -> m ()
Example without modifingM:
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Control.Lens (makeLenses, use, (.=))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Lazy (StateT(StateT), execStateT)
data GameObject = GameObject
{ _num :: Int
} deriving (Show)
data Game = Game
{ _objects :: [GameObject]
} deriving (Show)
makeLenses ''Game
makeLenses ''GameObject
defaultGame = Game {_objects = map GameObject [0 .. 3]}
action :: StateT Game IO ()
action = do
old <- use objects
new <- lift $ modifyObjects old
objects .= new
modifyObjects :: [GameObject] -> IO [GameObject]
modifyObjects objs = return objs -- do modifications
main :: IO ()
main = do
execStateT action defaultGame
return ()
This example works. Now I'd like to extract the code from action to a generic solution modifingM:
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Control.Lens (makeLenses, use, (.=), ASetter)
import Control.Monad.State.Class (MonadState)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Lazy (StateT(StateT), execStateT)
data GameObject = GameObject
{ _num :: Int
} deriving (Show)
data Game = Game
{ _objects :: [GameObject]
} deriving (Show)
makeLenses ''Game
makeLenses ''GameObject
defaultGame = Game {_objects = map GameObject [0 .. 3]}
modifyingM :: MonadState s m => ASetter s s a b -> (a -> m b) -> m ()
modifyingM l f = do
old <- use l
new <- lift $ f old
l .= new
action :: StateT Game IO ()
action = modifyingM objects modifyObjects
modifyObjects :: [GameObject] -> IO [GameObject]
modifyObjects objs = return objs -- do modifications
main :: IO ()
main = do
execStateT action defaultGame
return ()
This results in compile time errors:
Main.hs:26:14: error:
• Couldn't match type ‘Data.Functor.Identity.Identity s’
with ‘Data.Functor.Const.Const a s’
Expected type: Control.Lens.Getter.Getting a s a
Actual type: ASetter s s a b
• In the first argument of ‘use’, namely ‘l’
In a stmt of a 'do' block: old <- use l
In the expression:
do { old <- use l;
new <- lift $ f old;
l .= new }
• Relevant bindings include
f :: a -> m b (bound at app/Main.hs:25:14)
l :: ASetter s s a b (bound at app/Main.hs:25:12)
modifyingM :: ASetter s s a b -> (a -> m b) -> m ()
(bound at app/Main.hs:25:1)
Main.hs:31:10: error:
• Couldn't match type ‘IO’ with ‘StateT Game IO’
Expected type: StateT Game IO ()
Actual type: IO ()
• In the expression: modifyingM objects modifyObjects
In an equation for ‘action’:
action = modifyingM objects modifyObjects
What's the problem?
Edit 1: Assign new instead of old value.
Edit 2: Added example with solution of #Zeta that does not compile.
Edit 3: Remove example of second edit. It didn't compile due to wrong imports (see comment).
You're using use on a ASetter, but use takes a Getter:
use :: MonadState s m => Getting a s a -> m a
(.=) :: MonadState s m => ASetter s s a b -> b -> m ()
Unfortunately, ASetter and Getting are not the same:
type Getting r s a = (a -> Const r a ) -> s -> Const r s
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
We need to switch between Const and Identity arbitrarily. We need a Lens:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
Note that there is no f on the left-hand side. Next, we note that your lift is not necessary. After all, f already works in our target monad m; you had to use lift previously because modifyObjects was in IO and action was in StateT Game IO, but here we just have a single m:
modifyingM :: MonadState s m => Lens s s a a -> (a -> m b) -> m ()
modifyingM l f = do
old <- use l
new <- f old
l .= old
That works! But it's likely wrong, since you probably want to set the new value in l .= old. If that's the case, we have to make sure that old and new have the same type:
-- only a here, no b
-- v v v v
modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
modifyingM l f = do
old <- use l
new <- f old
l .= new
Keep in mind that you need to lift modifyObjects though:
action :: StateT Game IO ()
action = modifyingM objects (lift . modifyObjects)
We could stop here, but just for some fun, let us have a look again at Lens:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
For any a -> f b you give me, I'll give you a new s -> f t. So if we just plug something in your objects, we have
> :t \f -> objects f
\f -> objects f
:: Functor f => (GameObject -> f GameObject) -> Game -> f Game
Therefore, we just need some MonadState s m => (s -> m s) -> m () function, but that's easy to achieve:
import Control.Monad.State.Lazy (get, put) -- not the Trans variant!
modifyM :: MonadState s m => (s -> m s) -> m ()
modifyM f = get >>= f >>= put
Note that you need to use Control.Monad.State from mtl instead of Control.Monad.Trans.State. The latter only defines put :: Monad m => s -> StateT s m () and get :: Monad m => StateT s m s, but you want to use the MonadState variant from mtl.
If we put all things together, we see that modifyingM can be written as:
modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
modifyingM l f = modifyM (l f)
Alternatively, we use the can use the lens functions, although that does not give us the insight that we can use l f:
modifyingM :: MonadState s m => Lens s s a a -> (a -> m a) -> m ()
modifyingM l f = use l >>= f >>= assign l
I'm trying to run this freer code:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
module Lib where
import Control.Monad.Freer
import Control.Monad.Freer.Internal
import Control.Monad.Freer.State
import Data.IORef
runStateIO :: (Member (State s) r, Member IO r) => IORef s -> Eff (State s ': r) a -> Eff r (a,s)
runStateIO ref = loop
where
loop (Val v) = (v,) <$> send (readIORef ref)
loop (E u q) =
case decomp u of
Right Get -> send (readIORef ref) >>= loop . qApp q
Right (Put s) -> send (writeIORef ref s) >> loop (qApp q ())
Left u' -> E u' (tsingleton (loop . qApp q))
statefulFac :: Member (State Int) r => Int -> Eff r ()
statefulFac 1 = return ()
statefulFac n = do
a <- get
put (n * a)
statefulFac (n - 1)
runStatefulFac_IO :: Int -> IO Int
runStatefulFac_IO n = do
ref <- newIORef 1 :: IO (IORef Int)
let
-- none of these work:
-- stateful_fac :: (Member (State Int) r, Member IO r) => Eff r ()
-- stateful_fac :: Eff '[IO, State Int] ()
-- stateful_fac :: Eff '[State Int, IO] ()
stateful_fac = statefulFac 5
r = runStateIO ref stateful_fac
-- runM r
undefined
runStatefulFac_pure :: Int -> Int
runStatefulFac_pure n = snd (run (runState (statefulFac n) 1))
This is using stock freer, except I exported constructors of State effect to be able to run runStateIO interpreter. The problem is it's failing with
example.hs:42:11: error:
• Ambiguous type variable ‘r0’ arising from a use of ‘runStateIO’
prevents the constraint ‘(Data.Open.Union.Internal.Member'
(State Int)
r0
(Data.Open.Union.Internal.FindElem
(State Int) r0))’ from being solved.
Relevant bindings include
r :: Eff r0 ((), Int) (bound at example.hs:42:7)
stateful_fac :: Eff (State Int : r0) () (bound at example.hs:40:7)
Probable fix: use a type annotation to specify what ‘r0’ should be.
These potential instances exist:
two instances involving out-of-scope types
instance [safe] (r ~ (t' : r' : rs'),
Data.Open.Union.Internal.Member' t (r' : rs') n) =>
Data.Open.Union.Internal.Member'
t r ('Data.Open.Union.Internal.S n)
-- Defined in ‘Data.Open.Union.Internal’
instance [safe] r ~ (t : r') =>
Data.Open.Union.Internal.Member' t r 'Data.Open.Union.Internal.Z
-- Defined in ‘Data.Open.Union.Internal’
• In the expression: runStateIO ref stateful_fac
In an equation for ‘r’: r = runStateIO ref stateful_fac
In the expression:
do { ref <- newIORef 1 :: IO (IORef Int);
let stateful_fac = statefulFac 5
r = runStateIO ref stateful_fac;
undefined }
I'm wondering (1) what is this message trying to say (2) what's wrong with this code and how do I fix it? Thanks.
I think it's in this signature
runStateIO :: (Member (State s) r, Member IO r) => IORef s -> Eff (State s ': r) a -> Eff r (a,s)
You shouldn't have Member (State s) r in the constraint, since you are directly talking about State s ': r. So if you write
runStateIO :: ( Member IO r) => IORef s -> Eff (State s ': r) a -> Eff r (a,s)
then the signature you want in main should work
let stateful_fac = statefulFac 5 :: Eff '[State Int, IO] ()
The compiler was still looking to find a way for State Int to be a member of r after State Int had been 'interpreted'. So if I use the signature above for stateful_fac the compiler complains:
• Couldn't match type ‘'[]’ with ‘r'0 : rs'0’
arising from a use of ‘runStateIO’
rightly, since it knows there must be another element in there.
In the following example:
toss :: Double -> RVar Bool
toss p = do
q <- uniform 0 1
return $ q <= p
toss' :: MonadRandom m => Double -> m Bool
toss' p = runRVar (toss p) StdRandom
foo :: StateT Int RVar ()
foo = do
h <- lift $ toss' 0.5
if h then put 100 else put 0
bar :: StateT Int RVar ()
bar = do
h <- lift $ toss 0.5
if h then put 404 else put 200
testFoo :: MonadRandom m => m ((), Int)
testFoo = runRVar (runStateT foo 0) StdRandom
testBar :: MonadRandom m => m ((), Int)
testBar = runRVar (runStateT bar 0) StdRandom
I am confused about:
why the signature of foo is forced to be StateT Int RVar () even though toss' has signature m Bool for some MonadRandom m
Why testFoo has to be run with some StdRandom even though toss' already runRVar with StdRandom. It makes sense from a type perspective, but which StdRandom end up being used? If this question makes any sense at all.
Would it be possible to rewrite foo's signature as MonadRandom m => StateT Int m ()
When monomorphism restriction is on (it's on by default), all types of top-level binders will be inferred monomorphic.
You should add RVar to the monad stack like you did in bar. MonadRandom is too general.
StdRandom is just a data constructor, so it has no specific state. random-fu should handle updating state automatically.
Just add the signature. See 1.
What follows is a series of examples/exercises upon Lenses (by Edward Kmett) in MonadState, based on the solution of Petr Pudlak to my previous question.
In addition to demonstrate some uses and the power of the lenses, these examples show how difficult it is to understand the type signature generated by GHCi. There is hope that in the future things will improve?
{-# LANGUAGE TemplateHaskell, RankNTypes #-}
import Control.Lens
import Control.Monad.State
---------- Example by Petr Pudlak ----------
-- | An example of a universal function that modifies any lens.
-- It reads a string and appends it to the existing value.
modif :: Lens' a String -> StateT a IO ()
modif l = do
s <- lift getLine
l %= (++ s)
-----------------------------------------------
The following comment type signatures are those produced by GHCi.
The other are adaptations from those of Peter.
Personally, I am struggling to understand than those produced by GHCi, and I wonder: why GHCi does not produce those simplified?
-------------------------------------------
-- modif2
-- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
-- (Int -> p a b) -> Setting p s s a b -> t IO ()
modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()
modif2 f l = do
s<- lift getLine
l %= f (read s :: Int)
---------------------------------------
-- modif3
-- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
-- (String -> p a b) -> Setting p s s a b -> t IO ()
modif3 :: (String -> Int -> Int) -> Lens' a Int -> StateT a IO ()
modif3 f l = do
s <- lift getLine
l %= f s
-- :t modif3 (\n -> (+) (read n :: Int)) == Lens' a Int -> StateT a IO ()
---------------------------------------
-- modif4
-- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
-- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif4 :: (Bool -> Bool -> Bool) -> (String -> Bool) -> Lens' a Bool -> StateT a IO ()
modif4 f f2 l = do
s <- lift getLine
l %= f (f2 s)
-- :t modif4 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()
---------------------------------------
-- modif5
-- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
-- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif5 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif5 f f2 l = do
s<- lift getLine
l %= f (f2 s)
-- :t modif5 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()
---------------------------------------
-- modif6
-- :: (Profunctor p, MonadState s m) =>
-- (t -> p a b) -> (t1 -> t) -> t1 -> Setting p s s a b -> m ()
modif6 :: (b -> b -> b) -> (c -> b) -> c -> Lens' a b -> StateT a IO ()
modif6 f f2 x l = do
l %= f (f2 x)
-- :t modif6 (&&) (\s -> read s :: Bool) "True" == MonadState s m => Setting (->) s s Bool Bool -> m ()
-- :t modif6 (&&) (\s -> read s :: Bool) "True"
---------------------------------------
-- modif7
-- :: (Profunctor p, MonadState s IO) =>
-- (t -> p a b) -> (String -> t) -> Setting p s s a b -> IO ()
modif7 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif7 f f2 l = do
s <- lift getLine
l %= f (f2 s)
-- :t modif7 (&&) (\s -> read s :: Bool) ==
-- :t modif7 (+) (\s -> read s :: Int) ==
---------------------------------------
p7a :: StateT Int IO ()
p7a = do
get
modif7 (+) (\s -> read s :: Int) id
test7a = execStateT p7a 10 -- if input 30 then result 40
---------------------------------------
p7b :: StateT Bool IO ()
p7b = do
get
modif7 (||) (\s -> read s :: Bool) id
test7b = execStateT p7b False -- if input "True" then result "True"
---------------------------------------
data Test = Test { _first :: Int
, _second :: Bool
}
deriving Show
$(makeLenses ''Test)
dataTest :: Test
dataTest = Test { _first = 1, _second = False }
monadTest :: StateT Test IO String
monadTest = do
get
lift . putStrLn $ "1) modify \"first\" (Int requested)"
lift . putStrLn $ "2) modify \"second\" (Bool requested)"
answ <- lift getLine
case answ of
"1" -> do lift . putStr $ "> Write an Int: "
modif7 (+) (\s -> read s :: Int) first
"2" -> do lift . putStr $ "> Write a Bool: "
modif7 (||) (\s -> read s :: Bool) second
_ -> error "Wrong choice!"
return answ
testMonadTest :: IO Test
testMonadTest = execStateT monadTest dataTest
As a family in the ML tradition, Haskell is specifically designed so that every toplevel binding has a most general type, and the Haskell implementation can and has to infer this most general type. This ensures that you can reuse the binding in as much places as possible. In a way, this means that type inference is never wrong, because whatever type you have in mind, type inference will figure out the same type or a more general type.
why GHCi does not produce those simplified?
It figures out the more general types instead. For example, you mention that GHC figures out the following type for some code:
modif2 :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
(Int -> p a b) -> Setting p s s a b -> t IO ()
This is a very general type, because every time I use modif2, I can choose different profunctors p, monad transformers t and states s. So modif2 is very reusable. You prefer this type signature:
modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()
I agree that this is more readable, but also less generic: Here you decided that p has to be -> and t has to be StateT, and as a user of modif2, I couldn't change that.
There is hope that in the future things will improve?
I'm sure that Haskell will continue to mandate most general types as the result of type inference. I could imagine that in addition to the most general type, ghci or a third-party tool could show you example instantiations. In this case, it would be nice to declare somehow that -> is a typical profunctor. I'm not aware of any work in this direction, though, so there is not much hope, no.
Let's look at your first example:
modif :: Lens' a String -> StateT a IO ()
modif l = do
s <- lift getLine
l %= (++ s)
This type is simple, but it has also has a shortcoming: You can only use your function passing a Lens. You cannot use your function when you have an Iso are a Traversal, even though this would make perfect sense! Given the more general type that GHCi inferes, you could for example write the following:
modif _Just :: StateT (Maybe String) IO ()
which would append the read value only if that state was a Just, or
modif traverse :: StateT [String] IO ()
which would append the read value to all elements in the list. This is not possible with the simple type you gave, because _Just and traverse are not lenses, but only Traversals.