HNHacker News
TopNewBestAskShowJobs

jfecher

203 karma · joined June 17, 2017

submissionscomments
jfecher··on Ante: A low-level functional language
Ah, yes. The carousel has been a source of frustration especially on mobile for designing the website. Perhaps disabling swiping to scroll on the carousel on mobile would help. Or defaulting to use a dropdown to select the example on mobile instead.
jfecher··on Ante: A low-level functional language
I'm not a fan of python (it handles significant whitespace somewhat poorly). Cases like mixed tab-space whitespace and single-line only lambdas are python-specific problems for example.

I chose it mainly because I like the style and I haven't found it to be an issue in practice yet, especially with flexible rules for line continuations. The biggest detriment to me is the lack of auto-formatters for indentation.

Edit: I'll add to this a point I haven't seen discussed before about functional languages using the `f a b` syntax specifically. Without significant whitespace you cannot really have semicolon ellision with this syntax since almost any line may be interpreted as a function application of the next line. E.g. `a = b + c` and `foo 32` as subsequent lines would be interpreted as `a = b + (c foo 32)`, hence why ocaml requires semicolons. For some people semicolons are fine, but they're just noise to me :)

There's more detail on the specific significant whitespace scheme used on the website here: https://antelang.org/docs/language/#significant-whitespace

jfecher··on Ante: A low-level functional language
Lifetime inference compiles to destination passing so for most cases a single stack allocation in a prior function can be used. For your example of mutually recursive functions allocating in a loop the destination would be a region that will grow dynamically like an arena allocator. Since refs are typed, ref elements of the same type will be allocated next to each other in memory.

You don't touch on it, but there are some more difficult cases with lifetime inference as well. Namely branching the compiler must decide whether or not to extend a refs lifetime. This and a lack of granularity in container types are known problems with region inference (they lead to more memory than necessary being used by assuming the longest lifetimes), and are things I hope to tackle.

jfecher··on Ante: A low-level functional language
Hello, author here! Closures are implemented with the environment parameter as an extra parameter on a function type. So internally a function `i32 - i32 -> i32` (wonky function type syntax currently with - separating arguments) which uses an environment of type String is represented as a pair of the function and its environment: `(i32 - i32 - String -> i32), String`. The same way C++ and Rust represent their closures.

Function arguments are passed by value currently, though I may explore pass by move and other options in the future.

I hope for ante to be usable without a heap, though the `ref` type automatically handling lifetimes makes this more difficult. These refs compile to an equivalent of destination-passing in C, but can require dynamic allocation if a function creates a ref and another function calls the first in a loop. I also plan on having an Allocate effect for whenever a function can allocate, so that it is trivial to handle this with your own handle that can do anything. This would be an easier alternative to zig's approach of "pass the allocator everywhere manually," but there are still things I need to iron out, like how it interacts with the lifetime inference issues above, so its still in the design phase.

jfecher··on Ante: A low-level functional language
Author here, to me the lack of a pervasive tracing GC and values not being boxed by default are important for low level languages. That and maintaining the ability to drop down and use low level constructs like raw pointers for optimization or primitives for new abstractions are essential.
jfecher··on Ante: A low-level functional language
Author here, that is the purpose lifetime inference is meant to serve. It automatically extends lifetimes of `ref`s so that they are long enough. A key advantage of these is avoiding lifetime annotations in code, at the cost of some lack of control since the lifetime is increasingly handled for you. You can also opt out by using raw pointer types though and easily segfault with those.
jfecher··on Ante: A low-level functional language
It does not! It was meant to be a rather simple example showing what using iterators look like. Perhaps interesting to note that I'll likely be removing iterators in favor of generators which are easier to use. With monomorphisation of effects it is my hope that they'll be just as efficient, but Iterators will remain until tests prove that is the case.

As for the examples, they definitely showcase things that aren't present in most languages and can thus be confusing. That is on purpose though, since ante for me is a language to experiment with novel features I find interesting. The second example on lifetime inference for example can be thought of as like rust's lifetimes but completely inferred and instead of issuing errors for "x does not live long enough" it will instead automatically extend the lifetime of x. So my hope is it is easier to use at the loss of some control (control can be regained by using other pointer types like `Ptr a` for a raw pointer used to implement other things on top of).

jfecher··on Ante: A low-level functional language
String interpolation works by expanding to the concatenation of several strings. So a string like "the ${foo}." is expanded to "the " ++ foo ++ ".". There are some things I'd like to change about the current design. Namely it should probably defer to a StringBuilder of sorts, and it should possibly do it lazily so that interpolation can be used in log calls without worry of whether logging is enabled.

These are all checked at compile-time though, since interpolation can only be done inside string literals we can check for all uses of ${expr} inside literals and ensure e.g. that expr is convertable to a string. Since these are only in string literals, all placeholders are known in advance so there are none that cannot be checked at compile-time. (A string like "foo \${" ++ "foo}" must both escape the interpolation begin ${ and is concatenated to the actual string "foo ${foo}" with no interpolation. Otherwise it would be very confusing having interpolation happening in unintended circumstances (and would make string operations less efficient by having to check for this)).

jfecher··on Ante: A low-level functional language
Author here, a good way to understand algebraic effects is as "resumeable exceptions." In this case the expected_value handler says to run `f ()` and whenever that computation "throws" a `flip ()` effect to handle it by resuming the computation with the value true returned for flip. The continuation continues as normal, subsequent uses of flip are also handled until the computation finishs. Then we evaluate the rest of `+ resume false) / 2.0` and resume the computation (a second time!) in the same place, this time with the value false. Finally, we add both values returned and divide by two to get the expected value.

This way of computing expected value works because for each flip we essentially have a case-split: the value can be true or false with a 50-50 chance, so the expected value of the whole thing is is 0.5 * the result of the true branch + 0.5 * the result of the false branch!

This is more of a fun use of effects than a practical one. If you're still curious about effects, check out the full page on them here: https://antelang.org/docs/language/#algebraic-effects which includes some actually useful effects like State, Generators, looping constructs, parsers, etc.

jfecher··on Ante: A low-level functional language
Hello, author here! As the website says, the compiler itself is still in a very early state where basic things like functions, types, traits, inference, monomorphisation, and codegen are implemented. The fun stuff of algebraic effects, lifetime inference, and refinement types are not however. Though I can elaborate on implementation strategies of these for anyone curious. For example, algebraic effects in existing languages can be quite slow at runtime due to the use of continuations, the translation to a monad stack, and/or dynamically finding handlers at runtime. It is my plan to monomorphise them away and inline all these cases as in the following paper (1). With this, handlers are inlined into functions, continuations are normal closures, and continuations that aren't called multiple times are inlined.

(1) Zero-cost Effect Handlers by Staging: http://ps.informatik.uni-tuebingen.de/publications/schuster1...

jfecher··on Ante: a compile-time language
That's completely fine, negativity isn't inherently bad in the first place. I have done some work with a garbage collector for an interpreted language I had previously worked on before. It was very basic as far as garbage collectors go, it just had a simple mark-sweep algorithm, nothing too complex. I'm not denying that creating a garbage collector would be quite an ambitious process, my goal as a language developer is just to give enough control of the language to the user to make that possible.

That being said, by no means do I claim that all extensions will work together perfectly, or at all. For example, a garbage collector would be completely incompatible with a plugin that frees all variables once they go out of scope. Compatibility is left up to the plugin's author in the case of multiple plugins having conflicting goals.

I'm not sure where I stated I was opposed to making tradeoffs, I have done several (albeit mostly syntactic) already. I knew the project was ambitious when I first started it, that is part of the fun of working on it.

jfecher··on Ante: a compile-time language
Hello, author of the language here. You are correct that the project is an early state, I wasn't planning on spreading the word publicly for some time but as long as it was posted here I can answer some questions.

You can extend the compiler's functions during compile-time by doing more than just messing with llvm-ir. You are free to implement an automatic linter, or garbage collector for example. You can even do an optimization pass without operating on the llvm-ir. To do this, you could walk the parse tree of a function before it compiles and change around any common node patterns you are looking for before it is translated to llvm-ir. Alternatively, you can muck around in the ir and do the same thing.

The goto construct in the example provided does not need to be deeply integrated because the existing API is setup largely separate from the internal structure of the compiler. For example the function ctStore stores a variable in a compile-time container separate from the scope-separated table used internally by the compiler for other variables. The primary advantage of Ante as a language is that these functions enable compiler extensions to be made within the program and thus allow the swapping out of, eg. a garbage collector, without recompiling the compiler itself. This lets each application developer decide what features they need for their application. Instead of changing languages for a gc or desired optimization pass, just swap out a library.

I share your concern with the LLVM library constantly updating and the need to update bindings with it. I plan sometime to use the clang api to make a C++ -> Ante converter program, although I expect there to be numerous difficulties with its implementation.

jfecher··on Ante: a compile-time language
I decided to use it largely because of its proven track record, optimization passes, and its ability to act as a JIT compiler. LLVM is very competitive with other compiler's backends in terms of speed (see clang and gcc), and it is well established so there is plenty of help and documentation available. The builtin optimization passes means I can spend more time on the language itself rather than the IR, and the JIT, while not the fastest, is usable as is, and is extensible should I need it. Compared to using C as an intermediate language I would say there is a slight learning curve, but overall I found llvm easy to learn.

One of the only disadvantages is that because llvm is so large it requires some odd build steps that can be a pain to setup, especially on windows. The llvm-config tool however, manages many of required flags for you. Also, because there are so many libraries, compilation of the compiler itself can be rather slow.

jfecher··on Ante: a compile-time language
The Ante code to generate each portion of llvm-ir is checked as normal but there is no checking done on the llvm-ir that is generated. It is expected for each extension producing ir to generate valid output. That being said, one of the cool things about Ante (imo) is that if you dont like this, you can write an extension that does check for validity.
jfecher··on Ante: a compile-time language
Hello, I have been working on this project for a while now and would love to take any questions anyone may have.
← PreviousPage 2 of 2