Beta Release 0.1.11
This release brings even more core library uniformity though some breaking changes, some syntax refinements, and an exciting experimental feature: compiler extensions!
Syntactic Cleanup
There are several areas of the language that got a bit of a facelift in this release.
Doc-strings
Documentation strings were simplified in this release.
Instead of needing to use an ugly #doc
directive with a string literal, you can now use one or more ///
comments before the procedure.
// Old way
#doc """
This is a documentation string for this procedure.
"""
f :: () { }
// New way
/// This is a documentation string
/// for another procedure, that can
/// span multiple lines easily.
g :: () { }
Default cases
Specifying the default case on a switch statement was always a little weird.
It used to use the #default
directive that always looked out of place, but I struggled to think of a better syntax.
Now, you can simply use an underscore _
.
// Old way
switch value {
case 10 { ... }
case 20 { ... }
case #default { ... }
}
// New way
switch value {
case 10 { ... }
case 20 { ... }
case _ { ... }
}
Piping Placeholder
Previously when using the pipe operator, the left-hand side was always placed in the first argument slot. That was sometimes a pain when the argument you wanted to pipe into was in the wrong spot.
Now you can use an underscore as a placeholder argument and the pipe operator will place the argument in that position.
double :: x => x * 2
main :: () {
x := 10
x
|> double() // Places in the first slot
|> printf("Double x is {}\n", _) // Places in the second slot
}
Stabilized Optional Semicolons
Optional semicolons have been an opt in feature for about two months now.
I have been using them for every one of my projects and I have had no issues.
For this reason, optional semicolons are now enabled by default!
Having //+optional-semicolons
in your code does not hurt, but it is now just a comment with no meaning.
If you have any issues with the optional semicolons, feel compelled to open a GitHub issue documenting your problem and I will work to find a solution.
Breaking Changes
In order to make the provided core library functions more cohesive, sometimes breaking changes are necessary. These changes should not affect many programs, but they are worth discussing.
Iterator
uses ?T
Iterators are a core type used throughout many Onyx codebases.
Internally they are stored as a data pointer for internal state, and a next
procedure that takes the state and produces a value, or signals that the iterator is complete.
The next
procedure used to return (T, bool)
.
When the boolean was false, it signaled that the iterator was complete and the returned value should be ignored.
This has some weird semantics, because you would have to create an empty T
, even though it would never be used.
To fix this, next
now returns ? T
.
When the value is Some(T)
, the iterator is not done and the value can be used.
When the value is None
, the iterator is done and there is no way a value can be used.
This is a breaking change because it affects the implementation of every Iterator. While this is largely constrained to the core library, any custom iterators written will need to be updated. Thankfully this change is rather easy to make.
io.Stream
uses Result
In the same vein, the procedures in io.Stream
were updated to use the Result
type instead of relying on multiple return values in a pattern similar to Go's error handling.
This change should not affect many if any Onyx programs or libraries, but it still worth noting.
case
using range is no longer inclusive
This is the final breaking change, and has the potential for being quite impactful. When all things were considered, it made sense to make the breaking change now.
When you switch over an integer-like type, you can use a range in the case
to specify that any of the values in the range should match.
This range used to be inclusive, since it made intuitive sense when writing the following.
// Old way
s := "TeSt"
switch s[1] {
case 'a' .. 'z' do println("Is lowercase!")
case 'A' .. 'Z' do println("Is uppercase!")
}
The problem is that everywhere else in the language, a ..
range was not inclusive.
Since adding the inclusive-range operator last release, it makes sense to make a breaking change and change these case
statements to use ..=
instead.
// New way
s := "TeSt"
switch s[1] {
case 'a' ..= 'z' do println("Is lowercase!")
case 'A' ..= 'Z' do println("Is uppercase!")
}
This is slightly tricky thing to update, and is probably best handled by manually searching for all instances of switch
, since there is no compiler error if ..
is used. It will just be exclusive instead of inclusive.
Compiler Extensions
An experimental feature debuting this release is compiler extensions. Compiler extensions will allow user defined programs to interact with the compiler to generate auxiliary build files or supplement code generation.
The only use for compiler extensions at the moment is for procedural macros. These macros are expanded by a compiler extension that can do anything to generate the necessary code. More uses for compiler extensions will be added in the near future.
Take this theoretical example where the compiler extension generates bindings for an OpenAPI definition.
use core {*}
// Define the compiler extension by specifying the program to run.
// In this case "openapi_gen.wasm".
OpenAPIGenerator :: #compiler_extension "openapi_gen.wasm" {
generate_from_url
}
// Generate a structure for FooApi that contains methods
// corresponding to the API endpoints specified by the definition.
FooApi :: OpenAPIGenerator.generate_from_url!{"http://localhost:5000/api.json"}
// The above could generate:
//
// FooApi :: struct {
// foo :: (name: str) -> str { ... }
// }
main :: () {
// Invoke an endpoint as a function.
FooApi.foo("Hello")
}
Read more about compiler extensions on the docs.
Updating
To update to the newest version of Onyx simply use the same install script found on the homepage. It will automatically detect your previous install and will override it with the new version.
$ sh <(curl https://get.onyxlang.io -sSfL)
You can also run onyx self-upgrade
if you are on MacOS or Linux!
Happy programming!
Full Changelog
Additions
- Ability specify where piped arguments are placed using
_
. x |> foo(y, _) == foo(y, x)
- Alternative syntax for
case #default ...
. You can now just writecase _ ...
. - Alternative syntax for binding documentation using
///
. - **Experimental** compiler extensions feature, currently used to create procedural macros.
core.misc.any_deep_copy
- Ability to explicitly specify tag value for tagged unions.
Variant as value: type
, i.e.Foo as 3: i32
Removals
- Deprecated the use of
#default
in case statements. Use_
instead. iter.take_one
. Useiter.next
instead.
Changes
There are several breaking changes in this release related to core library APIs.
Iterator.next
now returns? T
instead of(T, bool)
io.Stream
usesResult(T, Error)
for return types instead of(Error, T)
switch
over arange
is no longer inclusive by default, since..=
exists now.- Enabled optional semicolons by default.
//+optional-semicolons
is no longer necessary.
There are also several non-breaking changes.
- The internal memory layout is different. See pull request #133 for details.