This is awesome! I know lots of people would love the type safety of a Haskell or OCaml combined with the metaprogramming and AST manipulation capabilities of a Lisp - and that seems to be the eventual goal of this project.
If Haskell's Template Haskell + cross compilation woes can be avoided (ideally cross compilation should be as easy as in Go's tool chain), while retaining all the other goodies, I'll have a new favorite programming language!
While a simple lisp-like macro system would indeed be convenient when requiring a tool to transform source code, it must be mentioned that metaprogramming as in generating large amount of code based on unpredictable input is better served by tools like metaocaml or, I've heard, template Haskell, which have no equivalent that I'm aware of in lisp world.
Exactly! I've been drooling over this kind of language in my dreams for quite a while now. Though I'd also like to see fully dependent types in there as well.
Nice to see Haskell and LISP ideas combined, that's inspiring and teaches the advances of 'the other' if you know only one of them. For a Prolog and Haskell marriage check out Curry: https://en.wikipedia.org/wiki/Curry_(programming_language)
"This is just what I wanted, all the syntactic peculiarities of Haskell with all the parentheses of Lisp." 'sarcasm.)
Is there a way that Hackett would standardize in using Readable Lisp [1] with Sweet-expressions as the default reader, to get rid of all the unneeded parentheses?
I'm curious, in which ways do you find Haskell syntax peculiar? Haskell has some of the cleanest syntax I've ever seen, not the least because tabs and newlines have meaning -- like in Python (which I also love).
I seem to harbor an unhealthy amount of hatred towards Lisp because of all the parentheses. The example shown at your link makes me visibly upset, unless I'm mentally prepared for it. I love the $ in Haskell, which lets me leave out parentheses, and I never use more than single level of nested parentheses, because the resulting code looks confusing to me.
I agree that Haskell syntax is incomparably clean, and that it works great for the classic simple program structures. But this laconism often makes it difficult to read.
I understand the power of point-free style, and it's quite exclusive to build pipelines. But it is very hard to read a function in that style if you're not already familiar with all the abstractions used.
The $ operator is a particular culprit ;-D Understanding a function depends heavily on precedence of operators. This is easy to do with algebra, because it uses few different operators. But in programming, where you have a huge number of functions defined in the program, it may be very hard to start reading a new program for the first time.
It seems to me like lisp-like (let's call this "somelisp", defined transpiled using Racket) syntax can be easily transpiled to other languages.
If in Haskell I have for example:
myFunc :: [a] -> Int
myFunc = length
main = do
print $ length "foo"
It seems like you could easy express this in "somelisp" with:
(myFunc :: [] a -> Int)
(myfunc = length)
(main = do
print (length "foo"))
And since you're using lisp-like syntax, you have the possibility to use macros now!
Now, I don't know if it would simply be simpler to encode these macros in your "somelisp" language defined using Racket, if you should make a Haskell extension, or if you should just not use it at all?
What about having macros defined in specific files, and use the target language, say Haskell in your code, but whenever you want to use a macro, use a syntax that Haskell does not accept: $(mymacro print $ length "foo") and have a preprocessor convert this macro to its corresponding haskell code?
I am currently learning Haskell, so I don't know if there are more complex examples where this will not work.
I know Haskell, and seeing your example Lisp code makes me completely dumbfounded: the parentheses look 100% redundant to me. What's the point of parenthesizing all expressions like that?
Do they work as a replacement for newlines, which are ignored? It looks a bit like C's semi-colons, except twice as bad.
Whilst undoubtedly cool, and based on super-interesting research, it's worth bearing in mind this is a compile-to-Racket language. Anyone who's used a compile-to-JS language will be familiar with the drawbacks of this approach.
Pretty much. Take the example in the post: the web server is Racket's web server with an FFI wrapper. So you're using an API that was never designed with Hackett's type system in mind. So now you've got the choice between exposing an efficient or an idiomatic interface. And so on...
I'm wondering how much "hacking" (e.g. use of undocumented functionality, modifying of existing code) is required to bolt this on an existing Haskell compiler.
> I have to admit I have no idea what racket is, nor did I do much more than scan the article.
why have you commented then? Mao Zedong had a dope saying about this, "No investigation, no right to speak!"
-----------------------------
One answer to your questions, btw, is that macros are useful and interesting regardless of what kind of type system you have. Haskell has something called Template Haskell, which is really quite bad for a number of reasons, but it is certainly possible to imagine a version of Haskell with a well-designed macro system. It just so happens that Racket has an exceptionally well-designed macro system, so the partnership of the two ideas is very plausible and scientifically interesting.
Macros and types are not in opposition. However, macros induce a notion of PHASE which is not usually accounted for in type systems, though it may be possible to account for this in a principled/type-theoretic way using ideas from modal logic and kripke semantics.
Could you explain what phase is? I understand haskell types, and I suspect Template haskell mostly works as an AST translator. A Google search on macros and phase, just turns up diet and bodybuilding preparation advice!
In the simplest case, a macro system gives rise to two-phase evaluation - first, there is code that runs at macro expansion time, that produces, as its result, new syntax, and second, that resulting code runs. But having just two phases is (needlessly) restrictive, because you might want macros that generate other macros (and so the macro-generating macros would need to run at an earlier phase than the generated ones).
Wouldn't the macro-expansion phase be run until it reaches a fixed point? That way all macros could be fully expanded before proceeding to the next phase.
So far as I'm aware, the drawbacks to Template Haskell are:
1) Debugging generated code can be a pain. To some degree this is true of most macro systems, but it certainly applies here.
2) It adds to build times, sometimes significantly.
3) There is an issue preventing use of Template Haskell when cross compiling.
Some other things of note:
The Haskell syntax is really complicated, compared to s-exprs. This is not entirely negative - variated syntax can help you know where you are in an expression and give more guidance when you mix things up - but it's not entirely positive either.
The stage restriction prevents using a piece of Template Haskell in the same module where you define it. For a lot of use cases, this doesn't matter... but it makes module-specific one-offs more awkward, moving their definition away from where they're relevant.
As of comparatively recent, there is "Typed Template Haskell". As originally conceived, Template Haskell generating an expression didn't "have to" produce an expression of the right type. Compilation would still fail once the type mismatch was detected, but further from the cause of the error.
Macros are absolutely not a way to get around the type system. The things macros let us do are mostly orthogonal to types and there is no contradiction to having macros in a typed language. In fact, that's exactly what Template Haskell is—a macro system for Haskell. It just happens to be awkward to use for a variety of reasons.
I would go further: a typed language needs the sort of metaprogramming macros provide to avoid having too much boilerplate and noise. You can either do this with a simple, general macro system or with ad-hoc features addressing narrow usecases a macro system would cover. Haskell's deriving mechanism, record system and syntax sugar like do-notation are all great examples of things that would be obviated with a solid macro system, making the language simpler.