Hacker News new | past | comments | ask | show | jobs | submit login
First Class Statements (jle.im)
48 points by tel on May 17, 2015 | hide | past | favorite | 13 comments



This isn't exactly true. For example, the article names declaration and assignment as among "statements", but such statements are not first class in Haskell. Nor are type, data, type class statements, etc. It is true that Haskell manages to accomplish many things with expressions that you'd use statements for in other languages.


Agreed, but there's a pretty straightforward distinction between statements amenable to becoming values and those that aren't. Essentially, those which control and define the type theory are out of scope as well as those which introduce bindings.


I hope that "not amenable to becoming values" is a transient state...


I'm not sure how I feel about it honestly. I suppose in a dependently typed language there'd be some allowance for `IO Type`.


I wouldn't call them statements because they aren't executed. Instead, I would classify them as "declarations". You state what something is, instead of telling the computer what to do.

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)


Somehow this doesn't seem correct. If statements were truly "first class", one should be able to programmatically put statements together and execute them (without using the loophole of simply calling the compiler).


I don't see how that's a "loophole". Your code has to tie into main somehow, but that's true of other languages too. Or you can call it from the repl. But it certainly doesn't make sense to talk about executing statements without bringing in the compiler or interpreter, in any language.

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.


> one should be able to programmatically put statements together and execute them

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.


To nitpick (and maybe derail this subthread a bit), you can do some pretty weird stuff with Python's numeric types. Unfortunately, the fascists who designed the language refuse to allow me to modify certain builtins...

  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.


Apparently Haskell does have a plugin that lets you eval expressions at runtime... from a string, though; is there a way to build an AST? (And what's the performance like?) Lisp of course would let you do something like

    (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.


There's two ways in which I'd like to address your questions here.

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).


You can programmatically put statements together, and manipulate them like real values. But that doesn't mean that pure simulated execution has to be a part of the API :)


[deleted]


They allow you to accomplish the same things, but having to wrap every statement in a function is a bit more awkward and carries a higher syntactic and conceptual cost. (It also doesn't let you manage effects through the type system, but that's another story altogether.)

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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: