Change syntax for null/errors
This commit is contained in:
parent
8b3f4a88c1
commit
a476fffe2c
Binary file not shown.
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
@ -11,19 +11,29 @@ To represent `null` we must use nullable types, represented
|
|||||||
by the question mark `?` character.
|
by the question mark `?` character.
|
||||||
|
|
||||||
For instance, a POST request may have a `username` parameter,
|
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={`
|
<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.
|
check if the value is null, and then use it.
|
||||||
|
|
||||||
|
The syntax `?` returns `true` if the value is not null.
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
if new_username != null {
|
if new_username?
|
||||||
|
{
|
||||||
// Here \`new_username\` is automatically casted to String
|
// 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
|
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={`
|
<Code thpcode={`
|
||||||
val favorite_color = null // Error, we must define the type
|
val favorite_color = null // Error, we must define the type
|
||||||
|
|
||||||
String? favorite_color = null // Ok
|
?String favorite_color = null // Ok
|
||||||
`} />
|
`} />
|
||||||
|
|
||||||
Other examples:
|
Other examples:
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
fun get_first(Array[String?] values) -> String? {}
|
fun get_first(Array[?String] values) -> ?String {}
|
||||||
|
|
||||||
val result = get_first([])
|
val result = get_first([])
|
||||||
`} />
|
`} />
|
||||||
|
|
||||||
## Optional chaining
|
## Optional chaining
|
||||||
|
|
||||||
If you have a `Type?` and you wish to access a field of `Type` if it exists,
|
If you have a `?Type` and you wish to access a field of `Type` if it exists,
|
||||||
you can use the optional chaining operator.
|
you can use the optional chaining operator `?.`.
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
Person? person = ...
|
?Person person = ...
|
||||||
|
|
||||||
val name = person?.name
|
val name = person?.name
|
||||||
`} />
|
`} />
|
||||||
@ -62,13 +72,41 @@ val name = person?.name
|
|||||||
- If `person` is not null, `person?.name` will return `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
|
## Elvis operator
|
||||||
|
|
||||||
The Elvis operator `??` is used to give a default value in case a `null` is found.
|
The Elvis operator `??` is used to give a default value in case a `null` is found.
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
// This is a function that may return a Int
|
// This is a function that may return a Int
|
||||||
fun get_score() -> Int? {...}
|
fun get_score() -> ?Int {...}
|
||||||
|
|
||||||
val test_score = get_score() ?? 0
|
val test_score = get_score() ?? 0
|
||||||
`} />
|
`} />
|
||||||
|
@ -12,14 +12,14 @@ and handled.
|
|||||||
|
|
||||||
## Declare that a function returns an exception
|
## Declare that a function returns an exception
|
||||||
|
|
||||||
To declare a possible error return value the `Result` enum
|
Possible errors have their own syntax: `Error!Type`.
|
||||||
is used.
|
This means: This may be an `Error`, or a `Type`.
|
||||||
|
|
||||||
For example, a function that returned a `DivisionByZero`
|
For example, a function that returned a `DivisionByZero`
|
||||||
may be written like this:
|
may be written like this:
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
fun invert(Int number) -> Int!DivisionByZero
|
fun invert(Int number) -> DivisionByZero!Int
|
||||||
{
|
{
|
||||||
if number == 0
|
if number == 0
|
||||||
{
|
{
|
||||||
@ -30,25 +30,22 @@ fun invert(Int number) -> Int!DivisionByZero
|
|||||||
}
|
}
|
||||||
`} />
|
`} />
|
||||||
|
|
||||||
In the previous segment, `Int!DivisionByZero` denotates
|
In the previous segment, `DivisionByZero!Int` denotates
|
||||||
that the function may return either an `Int` or an `DivisionByZero`.
|
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
|
### Multiple error returns
|
||||||
|
|
||||||
TODO: fix?
|
TODO: properly define syntax, how this interacts with type unions.
|
||||||
|
|
||||||
|
Multiple errors are chained with `!`. The last one is always
|
||||||
|
the success value.
|
||||||
If there are multiple error types that the function can return,
|
|
||||||
you can use the `|` operator:
|
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
type Exceptions = Exception1 | Exception2 | Exception3
|
fun sample() -> Error1!Error2!Error3!Int
|
||||||
|
|
||||||
fun sample() -> Int!Exceptions
|
|
||||||
{ /* ... */}
|
{ /* ... */}
|
||||||
`} />
|
`} />
|
||||||
|
|
||||||
@ -68,13 +65,13 @@ Use a naked `try` when you want to rethrow an error, if there is any.
|
|||||||
|
|
||||||
<InteractiveCode
|
<InteractiveCode
|
||||||
code={`
|
code={`
|
||||||
fun dangerous() -> Int!Exception
|
fun dangerous() -> Exception!Int
|
||||||
{ // May throw randomly
|
{ // May throw randomly
|
||||||
return if Math.random() < 0.5 { 50 }
|
return if Math.random() < 0.5 { 50 }
|
||||||
else { Exception("Unlucky") }
|
else { Exception("Unlucky") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun run() -> !Exception
|
fun run() -> Exception!
|
||||||
{ // If \`dangerous()\` throws, the function exits with the same error.
|
{ // If \`dangerous()\` throws, the function exits with the same error.
|
||||||
// Otherwise, continues
|
// Otherwise, continues
|
||||||
val result = try dangerous()
|
val result = try dangerous()
|
||||||
@ -156,7 +153,7 @@ Try/else will assign a new value if an expression fails.
|
|||||||
|
|
||||||
<InteractiveCode
|
<InteractiveCode
|
||||||
code={`
|
code={`
|
||||||
fun run(Int!Exception possible_value)
|
fun run(Exception!Int possible_value)
|
||||||
{
|
{
|
||||||
val mid = try possible_value else 666
|
val mid = try possible_value else 666
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user