[Haskell-cafe] Sequencing Operations in a Monad

SevenThunders mattcbro at earthlink.net
Sat Sep 15 14:33:18 EDT 2007

Ronald Guida wrote:
> I could translate your example to the following:
>> let S = A += B in
>>   do
>>       s <- S
>>       (r,c) <-  size (return s)
>>       k  <- matindex (return s)
> This should only perform action S one time.
That's a good point actually.  If I am careful about how I 'execute' my io
actions then I can avoid
unintended consequences.  Outside of the monad, however there is still a
referential transparency problem.  If I do something like this (+.  and -.
are my matrix addition and matrix subtraction operators)
  iou = a1 +. a2
  iov = a1 -. a2

then later
   u <- iou
   v <- iov

I have the unfortunate consequence of executing a1 and a2 twice.

> A simple design rule would be: A function should not take an IO action
> as an input if that action is to executed exactly once and only once.
Well my matrix addition with +. could satisfy this but still get me in  a
heap of trouble as shown above.
(+.) :: IO(Int) -> IO(Int) -> IO(Int)
Even worse with binary op.s you could do something like   let s = a +. a  . 
The bottom line is that
referential transparency goes out the door if your variables are IO actions. 
Perhaps this is a case where uniqueness types is better suited.

> This brings us back to sequencing.  Your "highly fluid" stack
> manipulations in C are exactly the thing that's bug-prone and anathema
> to functional programming.  Here's what I think you should do:
As far as bug prone, in the C world it works out quite nicely actually. 
First memory management is handled with nary a thought by managing the stack
and doing a little reference counting.  (For efficiency reasons I allow some
of matrices on my stack to be references into submatrices of other matrices
on the stack.)  Second I have a debug mode turned on by a define in the
include file that does detailed sanity checks on all input arguments.  I
just don't get segfaults due to bad pointer references because of this. 

I have already FFI'd all of this interface into Haskell, used Haskell as
kind of a glorified scripting language and have  written several complex
applications in Haskell using this interface with reasonable success.   Of
course all the matrix op.s and stack manipulations are done in the IO monad. 
It's just that now I want more :).  I want the syntax sugar so that I can
reason about matrix op.s more naturally and perhaps automate my handling of
my matrix stack a little easier.

> 1. Write a bunch of safe wrappers, as Ryan has described.
> 2. Test them thoroughly, and also *prove* that they are in fact safe.
> 3. Write your application-specific code and test it.  Do your best to
>   get your application-specific matrix calculations correct.
> 4. If the code is too slow then profile it.  Remember, 80% of the time
>   is usually spent in 20% of the code.  *IF* (and only if) the matrix
>   code happens to take up that 80% of the time, then proceed.
> 5. You can move your application-specific matrix calculations from
>   Haskell to C and put them behind an FFI interface.  Then, working
>   in C, you can optimize out all the matrix copying that takes place
>   behind safe wrappers.  The calculations will run faster without the
>   overhead of Haskell.

Well it certainly requires some thought here.  As I see it, I now have two
reasonable choices.  Either I pull all my matrix operations back inside the
IO monad and avoid the matrix action as a matrix variable paradigm  (due to
the loss of referential transparency)  or I devise some way to guarantee
'safety' and use unsafePerformIO.  I suppose I can use a somewhat
generalized version of safety where if I can guarantee that the order of
operations doesn't matter to the final output then I'm OK.  In this case if
I can make it so that reording the computations only reorders the locations
of my matrices on the stack, but otherwise doesn't affect the contents of
the matrices I think I am golden. 

I believe I got burned by following a nice tutorial interpretation of the IO
monad as a way of carrying around an undeclared state variable,  the world. 
But my little matrix IO variable is not just a world state with some matrix
data in it, rather it appears to be a world state with a chain of unapplied
function evaluations.  This is due to laziness I believe.  If I had a data
structure that looked more like a world state with a reference to a variable
in that world state, I could find a way to achieve my goals I think.
View this message in context: http://www.nabble.com/Sequencing-Operations-in-a-Monad-tf4446047.html#a12692656
Sent from the Haskell - Haskell-Cafe mailing list archive at Nabble.com.

More information about the Haskell-Cafe mailing list