Review of Haxe: Exploring the Features and Strengths of Haxe 4

A recent assessment of the Haxe programming language coincided with the then-upcoming release of Haxe 4. Now that Haxe 4 has officially launched (followed by bug-fix releases 4.0.1 and 4.0.2), it’s time for an updated evaluation. This review will explore the latest advancements in Haxe, the direction of the Haxe programming community, and the continued relevance of Haxe game engines.

Haxe 4’s New Features: A Review

Version 4 of Haxe, having undergone three years of development since the last major release, offers improvements in macro performance, developer experience, and syntax. Three notable enhancements, while still experimental, deserve special mention: a new JVM bytecode target, support for inline markup, and null safety checks.

Directly Compiling to JVM Bytecode: An Experimental Feature in Haxe 4

Haxe 4 introduces a new JVM bytecode target that streamlines Java development by eliminating the need for a separate compilation step using Java’s javac compiler. This enhancement simplifies the process of compiling Java source code generated by Haxe’s transpiler.

A comparison of the new direct JVM target with the original workflow when developing for a Java target. The original takes some .hx source, produces .java source, which in turn needs to be compiled with a Java compiler (which depends on the JDK) before a runnable .jar file is finally produced. The new target allows developers to go directly from .hx source to a runnable .jar file.

This direct compilation approach also removes the reliance on the Java Development Kit (JDK) and paves the way for future implementation of interactive debugging.

While awaiting the mainstream Haxe 4-compatible hxjava release, the setup process currently involves installing Haxe and Haxelib, followed by running the command haxelib install hxjava 4.0.0-alpha. Once completed, the development workflow becomes straightforward:

1
2
3
4
5
# transpile directly to JVM bytecode with Haxe (-D jvm would also work):
haxe --main HelloWorld --java jar_output --define jvm

# run JVM bytecode with Java:
java -jar jar_output/HelloWorld.jar

As direct JVM compilation remains experimental in Haxe 4, certain limitations exist:

Nevertheless, this development represents a significant advancement for developers working with Java-based technologies.

Haxe 4’s Experimental Inline Markup Support

Taking inspiration from JSX, Haxe 4 introduces inline markup, allowing developers to embed HTML directly within Haxe code:

1
2
3
4
5
6
    var dom = jsx(
      <div>
        <h1>Hello!</h1>
        <p>This is a paragraph.</p>
      </div>
    );

The ability to define jsx() as a static macro function enables compile-time checks for markup conformity to any XML-like specification. Utilizing Haxe’s built-in XML support, this check can leverage Xml.parse(), although for basic “XML-ish” parseability, not even that is needed:

1
2
3
4
5
6
7
8
  static macro function jsx(expr) {
    return switch expr.expr {
      case EMeta({name: ":markup"}, {expr: EConst(CString(s))}):
      	macro $v{"XML MARKUP: " + s};
      case _:
        throw new haxe.macro.Expr.Error("not an xml literal", expr.pos);
    }
  }

This feature aims to expand Haxe’s applicability beyond game development, although its relevance in that domain is undeniable. Implemented at the compiler level, it underscores a language-agnostic design. However, the responsibility of checking for specific DSLs falls upon the compiler team and the community to figure out.

Exploring Null Safety in Haxe 4

The concept of null references, introduced in 1965, has plagued developers working in nullable typed environments like Haxe. Aleksandr Kuzmenko estimates highlighting the prevalence of this issue with over 10 million GitHub commits dedicated to fixing null pointer reference errors.

Haxe 4 incorporates built-in compile-time null safety macros, activated by placing a @:nullSafety line before a definition. Two modes are available: @:nullSafety(Loose) (default) and @:nullSafety(Strict). Disabling the feature is possible using @:nullSafety(Off). In Strict mode, the compiler analyzes function calls for potential null assignments even within null safety-disabled contexts.

While Ruby developers might wonder about the inclusion of a safe navigation operator (like Ruby’s ?.), it is not yet available. However, as with many aspects of Haxe, there’s a macro for that (using !. instead).

Enhanced Developer Experience (DX) in Haxe 4: Syntax Additions, Syntactic Sugar, and Beyond

Haxe 4 introduces improvements to the language and its IDE support, bringing the developer experience on par with other programming languages. While aiming to cater to a wide range of developer needs, the compiler team prioritizes the integration of meaningful features and conventions from other languages.

This approach ensures that Haxe evolves without compromising its stability, practicality, and consistency. Not all changes in Haxe 4 will be considered revolutionary, but that’s precisely the point: DX enhancements take precedence over chasing fleeting trends.

However, Haxe doesn’t evolve in isolation. The changes reflect an understanding of patterns emerging in other languages, and Haxe 4 makes a conscious effort to appeal to developers familiar with more popular options.

The Introduction of New “Function Type” Syntax

In line with this philosophy, Haxe now supports two primary ways to represent function types. The older syntax, as explained by the original feature proposal, is somewhat misleading:

1
Int -> String -> Void

Haxe 4’s new syntax introduces named arguments, enhancing the developer experience:

1
(id:Int, name:String) -> Void

Beyond DX, adopting Haxe 4’s new function type syntax is recommended, as the older, less versatile syntax might be phased out in future major releases.

Syntactic Sugar and Other Improvements

While not revolutionary, the syntactic refinements in Haxe 4 will be welcomed by both existing Haxe developers familiar with languages like ES6 and newcomers transitioning to Haxe.

Arrow function (short lambda) syntax is now supported, essentially providing a shorthand for writing function and return. Key-value and index-value iteration syntaxes are also introduced for maps and arrays, respectively. Type declarations using static extensions can now utilize a single global using statement, eliminating the need for repetition wherever corresponding methods are used.

Enums and enum abstracts benefit from several improvements, including direct compiler support for the latter, replacing the previous reliance on macros. Similar advancements are seen in final classes, final interfaces, and extern fields.

While some features remain macro-dependent, they haven’t been overlooked. Operator overloading was levelled up to include field setters, and metadata can now be namespaced using . separators (e.g., @:prefix.subprefix.name).

Categorizing these changes solely as syntactic sugar might be an oversimplification, but interested readers can delve into the original proposals linked from Haxe 4’s release notes for detailed explanations.

Further DX Enhancements in Haxe 4

Although interactive debugging was already possible in Haxe for various compiled targets, the new eval target enables this functionality for interpreted code. As a simple demonstration, using any “Hello, World” Haxe tutorial project, one can create a file named whatever-you-want.hxml with the following content:

1
2
--main HelloWorld
--interp

This allows interactive debugging within the VSCode IDE by simply:

  1. Opening the project directory in VSCode;
  2. Setting a breakpoint; and
  3. Pressing F5 and selecting “Haxe Interpreter.”

This feature extends to interactive debugging of macro code, even when compiling for specific targets like java (instead of using --interp). The only prerequisite, apart from Haxe and VSCode, is the Haxe VSCode extension.

IDE Services Enhancements

Haxe 4 introduces a new IDE services protocol, already utilized in the latest VSCode Haxe extension, vshaxe. This protocol not only significantly improves performance but also facilitates substantial DX enhancements in vshaxe, including:

  • Long-awaited automatic imports
  • Autocompletion hover hints providing richer information, such as the origin of a field
  • Comprehensive autocompletion with features like expected type completion, postfix completion, and override completion
  • Keystroke-level optimizations for faster code writing

The excellent visual demos from the relevant vshaxe changelog offers a more comprehensive understanding of these features. While vshaxe with VSCode is not the only Haxe IDE available—HaxeDevelop and Kode Studio are dedicated options, and plugins exist for IntelliJ IDEA, Sublime Text, Atom, etc.—it seems to be at the forefront of utilizing Haxe 4’s IDE services protocol, closely followed by IntelliJ-Haxe.

Unicode Literals and Read-only Arrays

Support for true Unicode string literals is now available in Haxe 4, but developers should be mindful of certain some nuances. Additionally, the standard Haxe API now includes read-only arrays. These are straightforward to use; declaring a variable as haxe.ds.ReadOnlyArray<Int>, for instance, will result in compiler errors if attempts are made to modify the array. Adding the final keyword further restricts reassigning the array itself.

Call-site Inlining for Fine-grained Control

Call-site inlining, a new Haxe language feature, grants developers greater control over function inlining. This proves particularly beneficial when optimizing frequently called functions where the usual size-performance trade-off might be unfavorable.

These are just a few of the valuable enhancements in Haxe 4. The next section will explore what the Haxe community is building with these new tools.

Expanding Beyond Games: Web Development with Haxe 4

While game developers have historically constituted a significant portion of Haxe’s user base, there are numerous examples of Haxe’s successful implementation in other areas like business applications, mobile apps, and web development (both front-end and back-end).

In support of this, Haxe 4 offers regenerated HTML externs, updating Haxe’s js.html standard API to align with the broader web API as defined by MDN. This includes bug fixes and the addition of missing APIs (e.g., Haxe 4 now includes the Push API).

In his talk, Weaving a Better Web with Haxe, Juraj Kirchheim cites instances where Haxe-based web solutions demonstrate significantly improved efficiency and robustness in enterprise environments.

He also presents arguments against the Rails architectural approach (specifically folder hierarchy). However, for those who prefer a comprehensive web framework like Rails, there are still options available for find one. For those interested in exploring a complete web project built with Haxe, the public repo for Giffon, a crowdfunding platform supporting Haxe 4, provides a good starting point.

Furthermore, numerous web-focused, open-source Haxe libraries already support Haxe 4. Some examples include the JavaScript-splitting library Haxe Modular, the generic web request libraries thx.core and its sister libraries, and the established Haxe web toolkit Tinkerbell. The cross-platform UI solution HaxeUI, while supporting web contexts, targets a broader range of applications including business and desktop applications; its development has progressed steadily leading up to the Haxe 4 release.

Whether the focus is on web, games, or enterprise solutions, Haxe developers will eventually face the challenge of dependency management. A useful resource in this regard is the presentation by Adam Breece, Scaling well with others.

Is Haxe the Ultimate Programming Language for Game Development?

The question of whether a single “best” language for game development exists is subjective and often sparks passionate debates. Haxe’s success in the gaming world, surpassing what its community size might suggest, is undeniable. Joe Williamson, in his presentation an interview about winning the Ludum Dare 45 game jam in 2019, sheds light on the reasons behind this success, a trend likely to continue with Haxe 4.

Nicolas Cannasse, the creator of Haxe, is already utilizing Haxe 4 in the development of Shiro Games’ Northgard. Motion Twin has also adopted Haxe 4 for the development of Dead Cells. Both games have garnered tens of thousands of positive reviews on Steam and are available across multiple platforms, including PC (Windows, Mac, and Linux) and consoles. These achievements are impressive considering the relatively small development teams behind these games, which boast millions of players. Dead Cells is even available on iOS, with an Android version in development.

Several major Haxe game engines have embraced the changes in Haxe 4, demonstrating the commitment within the Haxe game development community. Examples of Haxe 4-compatible engines include Kha (and some of the engines built on top of it, like Armory), HaxeFlixel and its primary dependency OpenFL, NME, and Heaps (which powers Northgard and Dead Cells). Work is also underway to ensure compatibility with Haxe 4 for HaxePunk. In one instance, a library called Nape was specifically was forked to work with Haxe 4.

Some developers opt to create their own engines instead of using existing ones. Kirill Poletaev, who describes his experience in how and why, developed his own 3D Haxe game engine. Being an internal tool, it’s understandable that it hasn’t yet migrated to Haxe 4.

Haxe 4: Continuing a Legacy of Excellence and Stability

Haxe’s versatility means that the most impactful features of Haxe 4 will differ for each developer. This review doesn’t cover every single change. Some noteworthy omissions include:

  • Support for ES6 output in the JavaScript target
  • Removal of certain features (some accessible via the hx3compat library) and targets (PHP5 and soon AS3)
  • Standardization of CLI flags for consistency with common tools (e.g., -lib in .hxml files needs to be changed to -L or --library)
  • The words operator and overload are now reserved keywords, and final is now a keyword, preventing its use as a variable name.

Despite some breaking changes, their scarcity means that many actively maintained libraries haven’t needed to explicitly announce Haxe 4 compatibility. Migration from Haxe 3 is generally reported to be straightforward. After all, stability while supporting numerous target platforms is a core principle of Haxe, and Haxe 4 upholds this commitment.

Ultimately, it’s up to individual developers to decide whether Haxe is the ideal choice for their needs—whether for game development, robust web development libraries, or a superior development experience. However, Haxe undeniably remains a strong contender across various domains, offering a unique advantage for a wide range of developers.

For those interested in learning more, a Haxe tutorial by John Gabriele might be helpful. The release notes for Haxe 4.1.0 and later versions also provide valuable insights.

Licensed under CC BY-NC-SA 4.0