Hacker News new | ask | show | jobs
by mastax 1409 days ago
Haxe is really unusual and interesting, and I don't think it gets talked about enough.

> Haxe can build cross-platform applications targeting JavaScript, C++, C#, Java, JVM, Python, Lua, PHP, Flash, and allows access to each platform's native capabilities. Haxe has its own VMs (HashLink and NekoVM) but can also run in interpreted mode.

Compiling from one language to another isnt particularly unusual, but compiling from one language to so many is very unusual. On first impression it sounds unserious—real compilers output machine code—it's tempting to denigrate it by calling it a transpiler. But there are a lot of advantages that come with this approach. You always have access to the full capabilities of your target platforms. From a single language you can write code that is massively portable while also targeting specific platforms with just an if statement.

The "real" compiler authors spend months working on linkage, calling conventions, runtimes, symbol mangling, allocators, and debuginfo trying to get their native code to link properly to the objective-c frameworks on iOS—and it never feels quite right. If you instead compile to objective-c, a lot of things get easier. It's a very pragmatic approach.

4 comments

Haxe's transpiling helped me in a project once. The client needed a small-ish web app written ASAP, but they didn't know anything about their backend/stack when asked since they were non-technical and using an external provider for everything. The provider was not responding to emails for some reason, and the deadline was approaching, so I decided to start writing the thing in Haxe.

I figured they were either running PHP/SQL or Node, so I wrote a simple backend in a way that would make it easy for to deploy to either one with minimal changes. By the time the provider finally replied, the project was nearly half way complete. It turned out that they were using a standard PHP/SQL stack, so had I gone with Javascript there would've been problems. Instead, all I had to do was change one flag in my build system.

I don't know if this is a big selling point for Haxe since it's such a highly specific situation... but it's probably at least worth mentioning :P

Brilliant!
There's also that Haxe is older than a lot of the stuff we take for granted today. It's roots are in ActionScript, and it started as basically a successor to AS2 before Adobe came out with ActionScript 3. It did "same codebase on server and client" by compiling to flash bytecode and PHP before node existed. It's ECMAScript/AS roots + static types + type inference make it feel like alternate-timeline TypeScript as it also compiles to JS.

So it's completely comfortable for me. AND I can hit C++ if the platform demands it!

one aspect that's a bit underrated in haxe isn't just the cross-compilation, but the macro system - it's way more powerful a macro system than the regular macros found in C/C++. It's closer to a LISP macro, but only at compile time (rather than also runtime macros).

For example, you can define a json file, and have a macro that produce a class that matches that json file's fields. It would type-check (and you get auto-complete, for example, if you called that class's fields in another function). Of course, there's nothing special about a json file...why not use a live schema fetched from the internet! https://code.haxe.org/category/macros/completion-from-url.ht...

Another cool thing about macros is that, since the Haxe compiler itself implements a language server, you can go really crazy with AST manipulations and it will all show up in your IDE's autocomplete feature (if you have one).

There was a demo I saw a while back where someone added Google search suggestions to their IDE by hitting a Google endpoint in a Haxe macro.

In a similar vein, Elixir has what is basically compile-time-only macros (the macros being conceptually similar to Scheme's). You can transform the AST to arbitrary other AST, with hygiene, and of course, provides ways to reference the parent scope if needed. Macros are even based on the familiar quote and unquote system of lisp/scheme.

Caveat: I will note that technically speaking Elixir does not have a strictly separate compile time and runtime. However, in practice the language is used like it does. Compile time code is normally just a bunch of definitions of modules and macros and functions contained within. Executing the compile time code normally has no effect other than creating compiled modules. One can technically put things like loops or I/O etc at the top level, but that is normally only done if writing "scripts", rather than normal progarm code.

Thus for a normal program compiling it is basically just running an interpreter over the source code of the program. Indeed the elixir compiler executable is literally just the same as the interpreter used for scripts except with a flag to save compiled modules to disk rather than just leaving them in memory. (Plus a few other really minor differences that are not really relevant here.)

Runtime is then just asking the beam VM to execute the some function in the previously compiled module. While in theory one could try to define additional modules at runtime, this is not normally done. The language design strongly discourages this. If you need to generate modules programmatically, you are better off doing it as macros, which will run at compile time. Thus, normally the compiler is not used at runtime, and while I don't think elixir offers an option to remove the compiler at runtime, one could remove the underlying Erlang compiler at runtime, and without that the elixir compiler would just fail.

There are, of course, no artificial limitations in macros. They can run any code that could be run at runtime, in addition to modifying the AST, or defining new modules. In theory the macros could even present an interactive user interface, but obviously that would be absurd.

> The "real" compiler authors spend months working on linkage, calling conventions, runtimes, symbol mangling, allocators, and debuginfo

Don't worry: targeting high-level languages gives you a different, equally-frustrating set of problems! Actually, many of them have equivalents to the low-level ones: you also have to worry about calling conventions, name mangling, runtimes… they just look quite different.

Especially you want to support any kind of dynamic functionality, there are many uncomfortable trade-offs involved.

> On first impression it sounds unserious—real compilers output machine code—it's tempting to denigrate it by calling it a transpiler.

What? No, this is so wrong. “Real” compilers transform input from a source language to a target language. That’s it. A program could compile a language to itself with functions inlined and it would still be a compiler. Transpiler is a dumb word made up to identify compilers that output valid source for a chosen language.

Transpiler is a very useful word. It makes it clear that the translation doesn't happen from a high-level language to a low-level language as is usually implicitly assumed.

Arguing about the technical definition of compiler misses the point outside of a scientific paper. Connotations are more important than an arbitrary definition you happen to agreee with.