Change syntax for null/errors

This commit is contained in:
Araozu 2024-08-06 17:52:55 -05:00
parent 8b3f4a88c1
commit a476fffe2c
3 changed files with 62 additions and 27 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -11,19 +11,29 @@ To represent `null` we must use nullable types, represented
by the question mark `?` character.
For instance, a POST request may have a `username` parameter,
or it may not. This can be represented with an `String?`.
or it may not. This can be represented with an `?String`.
<Code thpcode={`
String? new_username = POST::get("username")
?String new_username = POST::get("username")
`} />
When we have a `Type?` we cannot use it directly. We must first
When we have a `?Type` we cannot use it directly. We must first
check if the value is null, and then use it.
The syntax `?` returns `true` if the value is not null.
<Code thpcode={`
if new_username != null {
if new_username?
{
// Here \`new_username\` is automatically casted to String
}
// you can also manually check for null
if new_username == null
{
// This is the same as above
}
`} />
We must check explicitly that the value is not null. Doing
@ -36,24 +46,24 @@ To create a nullable type we must explicitly annotate the type.
<Code thpcode={`
val favorite_color = null // Error, we must define the type
String? favorite_color = null // Ok
?String favorite_color = null // Ok
`} />
Other examples:
<Code thpcode={`
fun get_first(Array[String?] values) -> String? {}
fun get_first(Array[?String] values) -> ?String {}
val result = get_first([])
`} />
## Optional chaining
If you have a `Type?` and you wish to access a field of `Type` if it exists,
you can use the optional chaining operator.
If you have a `?Type` and you wish to access a field of `Type` if it exists,
you can use the optional chaining operator `?.`.
<Code thpcode={`
Person? person = ...
?Person person = ...
val name = person?.name
`} />
@ -62,13 +72,41 @@ val name = person?.name
- If `person` is not null, `person?.name` will return `name`
## Null unboxing
The `!!` operator transforms a `?Type` into `Type`.
If you are sure that a value cannot be `null`, you can force the
compiler to treat it as a regular value with the `!!` operator.
Note the two exclamation marks.
<Code thpcode={`
?String lastname = find_lastname()
// Tell the compiler trust me,
// I know this is not null
String s = lastname!!
`} />
You can use it to chain access:
<Code thpcode={`
val children_lastname = person!!.child!!.lastname
`} />
However, if at runtime you use `!!` on a null value,
the null value will be returned and your program will
blow up later. So make sure to use this operator
only when you are sure a value cannot be null.
## Elvis operator
The Elvis operator `??` is used to give a default value in case a `null` is found.
<Code thpcode={`
// This is a function that may return a Int
fun get_score() -> Int? {...}
fun get_score() -> ?Int {...}
val test_score = get_score() ?? 0
`} />

View File

@ -12,14 +12,14 @@ and handled.
## Declare that a function returns an exception
To declare a possible error return value the `Result` enum
is used.
Possible errors have their own syntax: `Error!Type`.
This means: This may be an `Error`, or a `Type`.
For example, a function that returned a `DivisionByZero`
may be written like this:
<Code thpcode={`
fun invert(Int number) -> Int!DivisionByZero
fun invert(Int number) -> DivisionByZero!Int
{
if number == 0
{
@ -30,25 +30,22 @@ fun invert(Int number) -> Int!DivisionByZero
}
`} />
In the previous segment, `Int!DivisionByZero` denotates
that the function may return either an `Int` or an `DivisionByZero`.
In the previous segment, `DivisionByZero!Int` denotates
that the function may return either a `DivisionByZero` error
or an `Int`.
We then can return the error or success value;
There is no `throw` keyword, errors are just returned.
### Multiple error returns
TODO: fix?
TODO: properly define syntax, how this interacts with type unions.
If there are multiple error types that the function can return,
you can use the `|` operator:
Multiple errors are chained with `!`. The last one is always
the success value.
<Code thpcode={`
type Exceptions = Exception1 | Exception2 | Exception3
fun sample() -> Int!Exceptions
fun sample() -> Error1!Error2!Error3!Int
{ /* ... */}
`} />
@ -68,13 +65,13 @@ Use a naked `try` when you want to rethrow an error, if there is any.
<InteractiveCode
code={`
fun dangerous() -> Int!Exception
fun dangerous() -> Exception!Int
{ // May throw randomly
return if Math.random() < 0.5 { 50 }
else { Exception("Unlucky") }
}
fun run() -> !Exception
fun run() -> Exception!
{ // If \`dangerous()\` throws, the function exits with the same error.
// Otherwise, continues
val result = try dangerous()
@ -156,7 +153,7 @@ Try/else will assign a new value if an expression fails.
<InteractiveCode
code={`
fun run(Int!Exception possible_value)
fun run(Exception!Int possible_value)
{
val mid = try possible_value else 666