fac n = do { a <- auto 1; i <- auto n; while (i >. 0) $ do { a *= i; i -= 1; }; a; }

runE (fac 10)

int fac(int n) { auto a = 1; auto i = n; while (i > 0) { a *= i; i -= 1; } return a; }

auto

auto

auto int i;

int i;

fac' n = do a <- newIORef 1 i <- newIORef n whileM (do i' <- readIORef i; return $ i' > 0) $ do i' <- readIORef i a' <- readIORef a writeIORef a (a' * i') writeIORef i (i' + 1) a' <- readIORef a return a'

readIORef

writeIORef

type V a ... -- variables of type a type E a ... -- expressions of type a (=:) :: V a -> E a -> IO () -- assignment plus :: E Int -> E Int -> E Int -- addition one :: E Int -- constant 1

x <- auto one x =: x `plus` x -- the following should be a type error (x `plus` x) =: one

x

V

E

V

E

data E' v a = ... data LValue data RValue type V a = E' LValue a type E a = E' RValue a

E'

auto

auto :: E a -> IO (E' v a)

x <- auto 2; ...

auto 2 >>= \ x -> ...

x

...

v

auto :: E a -> IO (forall v . E' v a)

x

forall v . E' v a

v

E

IO

E' RValue a

IO a

IO

runE

data E' v a where E :: IO a -> E' RValue a runE :: E' v a -> IO a runE (E t) = t

plus

one

E

plus :: E Int -> E Int -> E Int plus x y = E $ do x' <- runE x y' <- runE y return $ x' + y' one :: E Int one = E $ return 1

runE

auto

data E' v a where E :: IO a -> E' RValue a V :: IO a -> (a -> IO ()) -> E' v a runE :: E' v a -> IO a runE (E t) = t runE (V t _) = t auto :: E a -> IO (forall v . E' v a) auto x = do x' <- runE x r <- newIORef x' return (V (readIORef r) (writeIORef r))

V

(=:) :: V a -> E a -> IO () (V _ asg) =: e = do e' <- runE e asg e'

E

V a

E' LValue a

E

E' RValue a

(x `plus` x) =: one

Couldn't match expected type `LValue' against inferred type `RValue' Expected type: V a Inferred type: E Int

E

Num (E a)

E

E

E'

Here's a small Haskell program for computing factorial.It even runs,produces 3628800. Let's compare that to the C program doing the same thing.They look rather similar, don't they? (BTW, I decided to use the heavily underused C keyword. If you don't know C, don't worry,doesn't mean anything. A local declarationis the same asin C.) I often hear people complain that C-like programming in Haskell is ugly and verbose, but I don't think that has to be the case. What people complain about is when they write code like this:And I agree, it's ugly, it's verbose, but it's not necessary. One of the reasons it's verbose are the uses ofand. In C you just refer to the variable and you'll get an r-value when you need it, and you'll get an l-value when you need it. And you can do the same in Haskell. Let's look at a tiny version of the problem. We want the following operations:Here's a tiny sample:How can we make this happen? We wantto be able to be the left hand side of an assignment as well as an expression on the right hand side. But the assignment operator has different types on the left and right. So we could possibly makethe same as. But this would be bad, because we want the left hand side to be a variable and now we'd have no way of knowing, which means that it would no longer be a type error to assign to a non-variable. So, we need a little bit of type trickery. First, we'll makeand"subtypes" of the same type, so they have something in common.The typehas an additional type argument that determines if the value is an l-value or an r-value. So what about, what type should it have? It needs to return something that is both an l-value and an r-value. So we'll make it polymorphic! First attempt:But this won't work. Saying "" is the same as "". And when a variable is lambda bound it is not polymorphic. What does this mean? It means that we can useas either an l-value or as an r-value inside, but not both; thetype variable can only have a single type. Are we stuck? Well, we would have been without higher ranked polymorphism. But here's a type that works:So nowwill have typewhich is indeed polymorphic injust as we wanted. OK, so now we have some type signatures that work (using stub implementations it's easy to check that the types work out). So now we need to implement the types and the operations. Let's start with r-values. To make a constructor that only can construct r-values we'll use a GADT. Thetype will simply embed anvalue. So when we see the typeit's really isomorphic to. To extract thevalue we have the functionSo now we can doand; they are totally straightforward except that we need to unwrap and wrap theconstructor.And then the hard part, variables. To represent a variable we'll use two fields, one that reads the variable and one that assigns the variable. We need to extend thefunction to use the variable read field to get the value. Thefunction allocates a new variable and then packages up the two fields.We're about done, just assignment left. It's easy, just use the assignment field from theconstructor.Hmmm, but there's a missing case in that definition. What about the constructor? Can't we get a runtime failure? No. Look at the type of the first argument, it's, i.e.,. Theconstructor can only construct values of type, so we just can't get a match failure. Now our little example above compiles, and trying the bad expressiongives this error:Once we've made thedata type it's totally routine to make the factorial function I first showed work. We just need to create an instance forand a few more functions. Is this the end of the story? Is everything rosy? Is C programming in Haskell as smooth as that? Well, there are some flies in the ointment. While it's true that programming with thetype is easy, it's a little cumbersome if we want to use pure functions. Pute functions will not have thetype and will have to be lifted into this new brave world. Likewise, IO types will have to be lifted. So while we have reduced some verbosity, it has popped up again in a different place. Which is better? I don't know, but I thought I'd share the l-value/r-value trick. Oh, and (of course), there's nothing special about the IO monad. I just used it as an example; any monad with references will work. You can parametrizeover the underlying monad.

Labels: Haskell