[Haskell-beginners] adding state handing to existing code
Stephen Blackheath [to Haskell-Beginners]
mutilating.cauliflowers.stephen at blacksapphire.com
Mon Jan 25 16:18:11 EST 2010
Scott,
Generally your code is either written to be monadic or pure, but
"monadic" can be more or less specific depending on what typeclass is
used to say it's monadic. The most general is Monad m =>.
Here's a design where I've encountered this situation, which I think
might shed some light on the question: I did a web form editor for
server-side web applications. The whole thing was pure, but I realized
I had a problem: validators could not access the database.
The thing to do in this situation is to make the type something like:
processForm :: Monad m => Form m -> m Result
where 'Form m' can contain validators with a return type 'm (Either
String a)' in which validation errors are expressed as, e.g. Left "Value
must be positive!"
The logic of processForm can be as complex as you like, and written
completely purely (the generalness of the type Monad m => constrains the
logic of processForm to be pure), but validators that are passed in as
arguments can be any monad that the caller wants, e.g. able to access
the database. If a particular caller only uses pure validators,
processForm can be 'run' in the Identity monad.
Using typeclasses, the validators themselves can be expressed with
capabilities, e.g. validateKeyExists :: ReadableDatabase m =>
...something... -> m (Either String a)
This might mean that it can only read from the database, not write.
Then the whole thing would only typecheck if processForm was sequenced
in a monad that gave (at least) read access to the database.
Steve
Scott Thoman wrote:
> Thank you guys very much for the quick responses. This is very useful
> info and I've definitely got more reading and learning to do - in
> particular, the whole lifting/transforming/stacking monads area. Part
> of this exercise of learning Haskell is learning how to do things with
> a pure functional approach and so I'm thinking about things that might
> happen while building and maintaining software. In this scenario I
> was imagining that the "doit" function was something over which I
> might not have control. I like the idea that you can't just jam state
> into the code without the type system, and everyone involved, knowing
> about it. So my question is now evolving into looking at it the other
> way around - from the designer of "doit".
>
> If I were designing something, for a very simple example, like the map
> function but maybe over some custom sequence-like thing, how could I
> make it so that the user-supplied function could be monadic/stateful
> or not? Is there a way to make the map function flexible enough so
> that the author of the function argument could make it stateful or
> pure without any control over the definition of map itself?
>
> That question doesn't really need an answer that just my reasoning at
> this point - one of those "what if I had written software with Haskell
> and a high priority requirement came along, how would I handle it"
> kind of questions. It's an interesting exercise to think about
> applying a purely functional approach to something might happen in day
> to day development.
>
> -stt
>
> On Sun, Jan 24, 2010 at 4:11 PM, Stephen Blackheath [to
> Haskell-Beginners] <mutilating.cauliflowers.stephen at blacksapphire.com>
> wrote:
>> Scott,
>>
>> Here's the most straightforward way to do it:
>>
>> --
>> process :: Integer -> Integer -> StateT Int IO Integer
>> process x y = do
>> s <- get
>> put $ s + 1
>> return $ 2 * x * y
>>
>> doit :: StateT Int IO ()
>> doit = do
>> p <- process 42 43
>> liftIO $ printf "f x y = %d\n" p
>>
>> main :: IO ()
>> main = do
>> n <- execStateT doit 0
>> putStrLn $ "done "++show n++" times"
>> --
>>
>> One thing you'll note is that the type of 'doit' has changed. There's
>> no way to pass state "through" a function without it being reflected in
>> the type, and in many ways, that's the point of Haskell - to make
>> potentially dangerous things explicit. An alternative is to use an
>> IORef, but that makes your code completely imperative style, which is
>> not very Haskellish.
>>
>> One thing you'll notice is that process is now in IO, which is not
>> desirable, since it's pure. On occasions I've written this helper function:
>>
>> -- | Adapt a StateT to a pure state monad.
>> purely :: Monad m => State s a -> StateT s m a
>> purely code = do
>> s <- get
>> let (ret, s') = runState code s
>> put s'
>> return ret
>>
>> With this you could re-write it as...
>>
>> --
>> process :: Integer -> Integer -> State Int Integer
>> process x y = do
>> s <- get
>> put $ s + 1
>> return $ 2 * x * y
>>
>> doit :: StateT Int IO ()
>> doit = do
>> p <- purely $ process 42 43
>> liftIO $ printf "f x y = %d\n" p
>> --
>>
>> Monad transformer stacks aren't perfect, but they're good if used
>> appropriately. If you use them a lot, then it can lead to a necessity
>> to unstack and re-stack them like I did here. I think monads work best
>> if you initially think of your code in plain Haskell terms, and
>> introduce them later as a convenience.
>>
>> As I'm sure you know, the "Haskell way" is to make code as pure as
>> possible, using IO types only where necessary.
>>
>>
>> Steve
>>
>> Scott Thoman wrote:
>>> Since I'm very new to Haskell I have what is probably a simple
>>> question yet I'm having trouble finding a clear example of how it
>>> works. The basic question is: how do I pass state through existing
>>> code without the intermediate code knowing about it. If I have, for
>>> example, several layers of function calls and the innermost function
>>> needs to access some state that is only "seeded" by the outermost
>>> function, how do I do that without the functions in between knowing
>>> about the additional state being threaded through them?
>>>
>>> I have a simple example (that may *not* be good idiomatic Haskell):
>>>
>>> --
>>> process :: Integer -> Integer -> Integer
>>> process x y =
>>> 2 * x * y
>>>
>>> doit :: IO ()
>>> doit = do
>>> printf "f x y = %d\n" $ process 42 43
>>>
>>> main :: IO ()
>>> main = do
>>> doit
>>> putStrLn "done"
>>> --
>>>
>>> (I'm not totally sure about the type of "doit" but the code compiles
>>> and runs as expected)
>>>
>>> What I want to do is add some state handing to "process" to have it,
>>> say, count the number of times it's been called (putting
>>> threading/thread-local concerns aside for the moment). I'm trying to
>>> understand how to add state to "process" along the lines of:
>>>
>>> --
>>> process :: Integer -> Integer -> State Integer Integer
>>> process x y = do
>>> s <- get
>>> put $ s + 1
>>> return $ 2 * x * y
>>> --
>>>
>>> but I want to only seed the state from "main" without "doit" having to
>>> change -- I can call "process" from "doit" like "(execState (process
>>> 42 43) 0)" but I want the initial state to be determined at the top
>>> level, from main.
>>>
>>> I have a feeling there's some kind of "ah ha" moment that I'm just not
>>> seeing yet. Any help or pointers to where I can look for myself would
>>> be greatly appreciated.
>>>
>>> Thanks in advance,
>>>
>>> -thor
>>> _______________________________________________
>>> Beginners mailing list
>>> Beginners at haskell.org
>>> http://www.haskell.org/mailman/listinfo/beginners
>>>
>
More information about the Beginners
mailing list