
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) let iou = a1 +. a2 iov = a1 -. a2 then later do 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#a12692... Sent from the Haskell - Haskell-Cafe mailing list archive at Nabble.com.