The Dark code you write in your handlers is available as soon as you type (deployless). We have a number of built-in features to support this style of writing, as well as dealing with errors.
Dark executes code as you write it, and sometimes the code you execute won't be
fully written yet. We call code like this "incomplete". Anytime incomplete code
is executed, it results in a value of
Example: Here, the block has not been completed because the final expression
is not filled in. Therefore the handler returns
Incomplete values are allowed to co-exist with other values--whenever they
interact, the result is another
<Incomplete>. If they do not interract, often
the valid value will continue, perhaps being returned to your user in a HTTP
handler, or saved in a datastore.
Example: Here we left the query parameter blank in a call to
HttpClient::get. The result of the function is therefore
When something is incomplete, it will be shown with a red underline. An
expression may be incomplete even if it seems fine: this is because something it
depends on is incomplete. For example, in the case below we are returning
but never defined it:
In these cases, we provide a link to the source of the incomplete. Click on "click to locate source" and it will take you the source of the error.
The finer details
Incompletes are intended to allow you write code without disrupting the code
around it. You can add an empty
let statement -- which will have an
<Incomplete> value on the right -- and because that new value doesn't interact
with anything else, you code will continue to run just fine.
<Incomplete>s are ignored in lists. If you have an empty list, and
start to add an entry to that list, the new code will result in
until you have completed the code. The list will evaluate to
. This is so
that you can add entries to lists to without temporarily breaking your code.
The same is true of fields in a record: they will be ignored until the field's value is complete.
If a HTTP handler results in an
<Incomplete>, your user will get a 500 status
code with an error message.
When you see "invalid code", it means that your program is complete but has issues (which are almost always type error). The error message should have enough . Carefully read the message to see what's wrong.
Here we tried to use
DB::set with an Int for the
key parameter, even though
key needs to be a string.
To resolve this problem, we can use a string instead.
123 |> toString will both work.
The error rail is a unique feature of Dark. It's purpose is to allow you easily prototype code without having to deal with every error.
Background: Result and Option types
Dark has two types that model errors. The
Option type allows you to model a
value or the lack of a value. For example, when fetching a value from a
dictionary or Datastore, if the value doesn't exist, the function will return
Nothing. If it does exist, the function will return the value wrapped in a
In this case it unwraps to
Just val and the behavior would be the same for
functions that would return
Similarly, to handle errors, you can use a
Ok val wraps a successful
Error err wraps a failure value (possibly a string, or some
other value with information about the error).
Getting the value from an
Result can be irritating. You need to
match to check both possible values. This gives you certainty that you've
handled the different options, but means you need to do this cumbersome step for
any function which doesn't always succeed (which is a lot of them).
Prototyping using the Error Rail
When you are prototyping, you don't want to write tedious error checking code
that you might subsequently delete anyway. To make protying easier, functions
Result types can be automatically unwrapped. It's as
if these functions threw exceptions in the error case instead of using our more
structured error handling mechanism.
Functions that return
Result are automatically "put on the
errorrail" This means that they are automatically unwrapped in the success case.
In the failure case, execution stops in the handler or function and the bad
value is immediately returned.
A grey circle (🔘) on the right indicates that a success value was unwrapped.
A red error sign (🚫) on the right indicates that the function failed and that execution was stopped.
If the function has not yet been run, there will be a dotted line (⦙) on the right to indicate that an error could happen.
Handling errors exhaustively
While this is useful in prototyping, once you've figured out what your code is supposed to do, you'll want to handle potential errors. The errorrail indicators will show you places where an error might happen, allowing you to add error handling code.
When you are ready to handle error cases, you remove them from the rail by using
the editor command
take-function-off-rail (open the Command palette by hitting
Ctrl-\ on the function name). The result will no longer be unwrapped, and
you can handle the
Result value directly. You will typically do
this using a
See our sample canvas for examples.
Error rails with Incomplete and Type Errors
Incompletes and type errors are not the same as
Result types, and
cannot be used together. You cannot handle
<Incomplete>s or type error using
the error rail.