Recently I stumbled across a nice overview on how we can embed a C-style programming language with lvalues in haskell, and wondered why did no-one write a library for that?. And here I am.
Mind, this library is at the current stage a bit more than a hack. While it tries to make working with IORefs
(the Haskell equivalent of imperative languages stateful variables) a lot easier, it should not be used as a replacement for functional programming.
This library attempts to implement with the means of General Algebraic Data Types a type-safe version of lvalues. Your code won't compile if you try to use them wrong ¹.
To make it C-like, the module exports lvalues and rvalues on basic data types (basically wrapping IORef
s) and a nice C-like syntax for 1-D arrays.
Some operators have been defined but are meant to be used like the C ones (e.g. >, >=, ||, &&
etc.) and thus can clash with the prelude. Please import them qualified, or hide some Prelude functions.
That said, let's look at how to use it:
{-# LANGUAGE NoImplicitPrelude#-}
import LValue
main = do
x <- new 3
y <- copy x
y =: x * y
runExpression y >>= \val -> putStrLn "Now y contains" ++ show val
-- (x + y) =: new 42 -- Error: rvalues are not assignable
let z = constant 42
x += z
y += z
-- z += z -- Error: constants are rvalues, thus not assignable
runExpression y >>= \val -> putStrLn "Now y contains" ++ show val
Expected output:
Now y contains 9
Now y contains 51
Internally, x
, y
, z
are instances of Expr' v a
. v
is a phantom type used to make conversions between lvalues and rvalues possible in only one way.
The function runExpression
takes an expression in the form Expr' v a
and returns an IO a
containing the value.
How do new
, copy
and constant
differ? Simple:
new
generates a new lvalue given a naked valuecopy
copies an existing *value and copies it into a new lvalueconstant
generates a new rvalue given a naked value. It does not create a newIORef
internally and can be conceptually seen as a C-styleconst
(i.e. not modifiable, not necessarily a constant literal).
- Add an example for arrays
- Implement while loops
- Implement for loops
- Make some IO wrappers
- Make it work outside the IO monad (e.g. ST monad)
- Possibly integrate Template Haskell
¹: At this stage, some operations rely on runtime checks, for example array indexing requires the lists to only have one element.