code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(123 lines)

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:

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:

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:

x : [Text]? = none

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 declare the type to be optional, but assign a non-none value:

x : Int? = 5
# Later on, assign none:
x = none

Type Inference

For convenience, none is an optional value whose type is inferred from the context where it's used. Some examples are:

  • 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:

x : Int?
x = none

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

doop(none)

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():

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

maybe_x = none
assert (maybe_x or -1) == -1
>> 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".

1 # Optional Values
3 A very common use case is values that may or may not be present. You could
4 represent this case using enums like so:
6 ```tomo
7 enum MaybeInt(AnInt(x:Int), NoInt)
9 func maybe_takes_int(maybe_x:MaybeInt)
10 when maybe_x is AnInt(x)
11 say("Got an int: $x")
12 else
13 say("Got nothing")
14 ```
16 However, it's overly onerous to have to define a separate type for each
17 situation where you might want to not have a value. Instead, Tomo has
18 built-in support for optional types:
20 ```
21 func maybe_takes_int(x:Int?)
22 if x
23 say("Got an int: $x")
24 else
25 say("Got nothing")
26 ```
28 This establishes a common language for talking about optional values without
29 having to use a more generalized form of `enum` which may have different naming
30 conventions and which would generate a lot of unnecessary code.
32 ## Syntax
34 Optional types are written using a `?` after the type name. So, an optional
35 integer would be written as `Int?` and an optional list of texts would be
36 written as `[Text]?`.
38 None can be written explicitly using `none` with a type annotation. For
39 example, if you wanted to declare a variable that could be either an integer
40 value or `none` and initialize it as none, you would write it as:
42 ```tomo
43 x : Int = none
44 ```
46 Similarly, if you wanted to declare a variable that could be a list of texts
47 or none and initialize it as none, you would write:
49 ```tomo
50 x : [Text]? = none
51 ```
53 If you want to declare a variable and initialize it with a non-none value, but
54 keep open the possibility of assigning `none` later, you can declare the type
55 to be optional, but assign a non-none value:
57 ```tomo
58 x : Int? = 5
59 # Later on, assign none:
60 x = none
61 ```
63 ## Type Inference
65 For convenience, `none` is an optional value whose type is inferred from the
66 context where it's used. Some examples are:
68 - When assigning to a variable that has already been declared as optional.
69 - When returning from a function with an explicit optional return type.
70 - When passing an argument to a function with an optional argument type.
72 Here are some examples:
74 ```tomo
75 x : Int?
76 x = none
78 func doop(arg:Int? -> Text?)
79 return none
81 doop(none)
82 ```
84 ## None Checking
86 In addition to using conditionals to check for `none`, you can also use `or` to
87 get a non-none value by either providing an alternative non-none value or by
88 providing an early out statement like `return`/`skip`/`stop` or a function with
89 an `Abort` type like `fail()` or `exit()`:
91 ```tomo
92 maybe_x : Int? = 5
93 assert (maybe_x or -1) == 5
94 assert (maybe_x or fail("No value!")) == 5
96 maybe_x = none
97 assert (maybe_x or -1) == -1
98 >> maybe_x or fail("No value!")
99 # Failure!
101 func do_stuff(matches:[Text])
102 pass
104 for line in lines
105 matches := line.matches($/{..},{..}/) or skip
106 # The `or skip` above means that if we're here, `matches` is non-none:
107 do_stuff(matches)
108 ```
110 ## Implementation Notes
112 The implementation of optional types is highly efficient and has no memory
113 overhead for pointers, collection types (lists, sets, tables), booleans,
114 texts, enums, nums, or integers (`Int` type only). This is done by using
115 carefully chosen values, such as `0` for pointers, `2` for booleans, or a
116 negative length for lists. However, for fixed-size integers (`Int64`, `Int32`,
117 `Int16`, and `Int8`), bytes, and structs, an additional byte is required for
118 out-of-band information about whether the value is none or not.
120 Floating point numbers (`Num` and `Num32`) use `NaN` to represent none, so
121 optional nums should be careful to avoid using `NaN` as a non-none value. This
122 option was chosen to minimize the memory overhead of optional nums and because
123 `NaN` literally means "not a number".