First Class Statements
blog.jle.im
blog.jle.im
Traditional assignment is an expression in Haskell.
count :: Int -> IO Int
count n = do r <- newIORef 0
loop r 1
where
loop :: IORef Int -> Int -> IO Int
loop r i | i>n = readIORef r
| otherwise = do v <- readIORef r
writeIORef r (v+i)
loop r (i+1)You can take a list of IO actions ("statements") and use the sequence function to get a single statement out.
sequence :: Monad m => [m a] -> m [a]
Then you can execute the result by having it in the flow of your program, or pass it around just like any other statement.The list of statements can be put together however you like.
I'm not sure I understand. In Haskell, you can programmatically put statements together. You can't execute them willy-nilly just like how you can't try to execute an integer in python. You just create make a statement called "main" and the program is compiled so as to execute that statement.
AttributeError: 'int' object has no attribute '__call__'
I mean, of course it doesn't, that's what I'm trying to fix.At one point, I was desirous of having floating point numbers whose value decayed toward zero each time the number was accessed; you can achieve this by defining a custom class and implementing all the methods needed to make it behave like a float, but it's not the same.
(do-something-with '(+ a b))
and do-something-with could do any arbitrary transformation on the AST, possibly ending up passing it to eval, possibly optimizing it and compiling it to some other language... while Haskell's a leap in this regard compared to imperative languages, I think only something like Lisp can call its code truly first-class.First, directly addressing the question you asked:
There are a number of libraries for assembling and manipulating a Haskell AST - I would recommend looking at haskell-src-exts and Template Haskell. Unfortunately, there doesn't seem to be a way to exec any of these directly (at runtime). You could obviously pretty-print and then eval that, which may or may not be a good fit depending on your problem space.
Separately, I would like to suggest a different perspective, in line with the thesis of the article.
(Pardon me if this gets a bit rambly, and feel more than free to ask questions if it gets confusing - I've been meaning to expand on this in a blog post...)
Haskellers are fond of monads. Lispers are fond of S-expressions. Lisp programs are S-expressions. S-expressions are either an atom, or a list of S-expressions - a structure also known as a "rose tree".
It turns out, a rose tree is a monad. In particular, a "free monad", which means there's a unique function turning a rose tree into any other monad in a way that preserves structure (in math terms, a "monad homomorphism"). Getting slightly speculative, I think the homomorphism from Free to IO has to be Lisp's eval.
So in a senes, when Haskellers write functions that operate on "Monad m", they are writing functions that can operate on Lisp programs, and also on many "partial interpretations" or "restricted forms" of Lisp programs. This is why Haskell programmers get about as excited about monads as Lisp programmers do about macros - there's a deep connection.
The big thing that Haskell lacks, in this context, is a mutable global namespace - which keeps a few things compile-time only (and hence the need for Template Haskell for fuller coverage of metaprogramming).
Deleted comment
It's the same as the difference between having lambdas and having Java's anonymous inner classes with a .run() method. Yes, it allows you to do the same sorts of things, but it adds some extra unneeded complexity and is significantly more awkward. As a result, people simply don't use them as much and Java, on the whole, becomes effectively less expressive and readable.
You can see this particularly in languages with unwieldy lambda syntax like JavaScript: people are much less likely to make custom control structures that accept functions because wrapping every statement in function () { ... return x ... } is so unappealing. And when they do (for useful things like executing a bunch of things concurrently or pattern matching), the code is much more awkward than it has to be.
This could be partially mitigated with a sane lambda syntax, but it still wouldn't be ideal. You'd also need some low-profile way of composing functions like this—like, say, an operator that takes two functions and gives you one that does both of them. And then, all of a sudden, you've reinvented like 60% of the relevant Haskell feature!
It's also worth noting that, in Haskell particularly, functions for this simply don't make sense. They would be foreign to Haskell's model. Haskell is non-strict so functions do not control evaluation order; they are simply abstractions over terms.