[Haskell-beginners] adding state handing to existing code
Scott Thoman
scott at thoman.org
Mon Jan 25 16:34:28 EST 2010
Now that sounds like what i was imagining too where processForm is
"prepared" to handle the case where the user wants monadic stuff but
can deal with the case where they're pure too - leaving processForm
pure and unchanged.
There sure is a lot to learn with this Haskell stuff.
-stt
On Mon, Jan 25, 2010 at 4:18 PM, Stephen Blackheath [to
Haskell-Beginners] <mutilating.cauliflowers.stephen at blacksapphire.com>
wrote:
> 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
>>>>
>>
>
--
"Very well, then, Mr. Knox, sir.
Let's have a little talk about tweetle beetles...."
- Dr. S.
More information about the Beginners
mailing list