aboutsummaryrefslogtreecommitdiff
path: root/docs/optionals.md
blob: bfebd94e93982908007606ca938cc6cc320fd928 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Optional Values

A very common use case is values that may or may not be present. You could
represent this case using enums like so:

```tomo
enum MaybeInt(AnInt(x:Int), NoInt)

func maybe_takes_int(maybe_x:MaybeInt)
    when maybe_x is AnInt(x)
        say("Got an int: $x")
    else
        say("Got nothing")
```

However, it's overly onerous to have to define a separate type for each
situation where you might want to not have a value. Instead, Tomo has
built-in support for optional types:

```
func maybe_takes_int(x:Int?)
    if x
        say("Got an int: $x")
    else
        say("Got nothing")
```

This establishes a common language for talking about optional values without
having to use a more generalized form of `enum` which may have different naming
conventions and which would generate a lot of unnecessary code.

## Syntax

Optional types are written using a `?` after the type name. So, an optional
integer would be written as `Int?` and an optional list of texts would be
written as `[Text]?`.

None can be written explicitly using `none` with a type annotation. For
example, if you wanted to declare a variable that could be either an integer
value or `none` and initialize it as none, you would write it as:

```tomo
x : Int = none
```

Similarly, if you wanted to declare a variable that could be a list of texts
or none and initialize it as none, you would write:

```tomo
x := ![Text]
```

If you want to declare a variable and initialize it with a non-none value, but
keep open the possibility of assigning `none` later, you can use the postfix
`?` operator to indicate that a value is optional:

```tomo
x := 5?
# Later on, assign none:
x = none
```

## Type Inference

For convenience, `none` can also be written without the explicit type
annotation for any type in situations where the compiler knows what type of
optional value is expected:

- When assigning to a variable that has already been declared as optional.
- When returning from a function with an explicit optional return type.
- When passing an argument to a function with an optional argument type.

Here are some examples:

```tomo
x := 5?
x = none

func doop(arg:Int? -> Text?)
    return none

doop(none)
```

Non-none values can also be automatically promoted to optional values without
the need for an explicit `?` operator in the cases listed above:

```tomo
x : Int? = none
x = 5

func doop(arg:Int? -> Text?)
    return "okay"

doop(123)
```

## None Checking

In addition to using conditionals to check for `none`, you can also use `or` to
get a non-none value by either providing an alternative non-none value or by
providing an early out statement like `return`/`skip`/`stop` or a function with
an `Abort` type like `fail()` or `exit()`:

```tomo
maybe_x := 5?
>> maybe_x or -1
= 5 : Int
>> maybe_x or fail("No value!")
= 5 : Int

maybe_x = none
>> maybe_x or -1
= -1 : Int
>> maybe_x or fail("No value!")
# Failure!

func do_stuff(matches:[Text])
    pass

for line in lines
    matches := line.matches($/{..},{..}/) or skip
    # The `or skip` above means that if we're here, `matches` is non-none:
    do_stuff(matches)
```

## Implementation Notes

The implementation of optional types is highly efficient and has no memory
overhead for pointers, collection types (lists, sets, tables), booleans,
texts, enums, nums, or integers (`Int` type only). This is done by using
carefully chosen values, such as `0` for pointers, `2` for booleans, or a
negative length for lists. However, for fixed-size integers (`Int64`, `Int32`,
`Int16`, and `Int8`), bytes, and structs, an additional byte is required for
out-of-band information about whether the value is none or not.

Floating point numbers (`Num` and `Num32`) use `NaN` to represent none, so
optional nums should be careful to avoid using `NaN` as a non-none value. This
option was chosen to minimize the memory overhead of optional nums and because
`NaN` literally means "not a number".