You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: docs/src/usage.md
+30-9
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,10 @@
1
1
# Usage
2
2
3
3
### The Result type
4
-
Fundamentally, we have the two types `Ok{T}` and `Err{E}`, each just being wrapper types of a `T` or `E`, respectively. These represent a successful value of type `T` or an error of type `E`. For example, a function returning either a string or a 32-bit integer error code could return a `Result{String, Int32}`.
5
-
6
-
`Ok` and `Err` are not supposed to be instantiated directly (and indeed, cannot be easily instantiated). Instead, they only exist as fields of a `Result{T, E}` type, which contains a field typed `Union{Ok{T}, Err{E}}`. You can construct it like this:
4
+
Fundamentally, we have `Result{T, E}`. This type _contains_ either a successful value of type `T` or an error of type `E`.
5
+
For example, a function returning either a string or a 32-bit integer error code could return a `Result{String, Int32}`.
7
6
7
+
You can construct it like this:
8
8
```
9
9
julia> Result{String, Int}(Ok("Nothing went wrong"))
10
10
Result{String, Int64}(Ok("Nothing went wrong"))
@@ -31,7 +31,10 @@ Option{Integer}(some(1))
31
31
```
32
32
33
33
#### ResultConstructor
34
-
Behind the scenes, calling `Err(x)` or `Ok(x)` creates an instance of the non-exported type `ResultConstructor`.
34
+
Internally, `Result{T, E}` contains a field typed `Union{Ok{T}, Err{E}}.`
35
+
The types `Ok` and `Err` are not supposed to be instantiated directly (and indeed, cannot be easily instantiated).
36
+
37
+
Calling `Ok(x)` or `Err(x)` instead creates an instance of the non-exported type `ResultConstructor`:
35
38
36
39
```
37
40
julia> Err(1)
@@ -41,7 +44,8 @@ julia> Ok(1)
41
44
ErrorTypes.ResultConstructor{Int64, Ok}(1)
42
45
```
43
46
44
-
The only purpose of `ResultConstructor` is to create `Result`s with the correct parameters, and to allow conversions of carefully selected types. The user does not need to think much about `ResultConstructor`, but if `ErrorTypes` is abused, this type can show up in the stacktraces.
47
+
The only purpose of `ResultConstructor` is to more easily create `Result`s with the correct parameters, and to allow conversions of carefully selected types, read on to learn how.
48
+
The user does not need to think much about `ResultConstructor`, but if `ErrorTypes` is abused, this type can show up in the stacktraces.
45
49
46
50
`none` by itself is a constant for `ErrorTypes.ResultConstructor{Nothing, Err}(nothing)` - we will come back to why this is particularly convenient.
Note in the first example that `none` can be returned, which was a generic instance of `ResultConstructor`. One purpose of `ResultConstructor` is that annotating the return type will cause this generic `none` to automatically convert to `Option{Float64}`. Similarly, one can also use a typeassert to ease in the construction of `Result`:
65
+
When annotating a function with a return type `T`, the return value gets converted at the end with an explicit `convert(T, return_value)`.
66
+
67
+
In the function in this example, the function can return `none`, which was a generic instance of `ResultConstructor`.
68
+
When that happens, `none` is automatically converted to the correct value, in this case `none(Float64)`.
69
+
Similarly, one can also use a typeassert to ease in the construction of `Result` return type:
62
70
63
71
```
64
72
function get_length(x)::Result{Int, Base.IteratorSize}
@@ -71,7 +79,10 @@ function get_length(x)::Result{Int, Base.IteratorSize}
71
79
end
72
80
```
73
81
74
-
In this example, `Ok(Int(length(x))` returns a `ResultConstructor{Int, Ok}`, which can be converted to the target `Result{Int, Base.IteratorSize}`. Similarly, the `Err(isz)` creates a `ResultConstructor{Base.IteratorSize, Err}`, which can likewise be converted.
82
+
In the above example example, `Ok(Int(length(x))` returns a `ResultConstructor{Int, Ok}`, which can be converted to the target `Result{Int, Base.IteratorSize}`.
83
+
Similarly, the `Err(isz)` creates a `ResultConstructor{Base.IteratorSize, Err}`, which can likewise be converted.
84
+
85
+
In most cases therefore, you never have to constuct an `Option` or `Result` directly. Instead, use a typeassert and return `some(x)` or `none` to return an `Option`, and return `Ok(x)` or `Err(x)` to return a `Result`.
75
86
76
87
### Conversion rules
77
88
Error types can only convert to each other in certain circumstances. This is intentional, because type conversion is a major source of mistakes.
@@ -80,9 +91,13 @@ Error types can only convert to each other in certain circumstances. This is int
80
91
* A `ResultConstructor{T, Ok}` can be converted to `Result{T2, E}` if `T <: T2`.
81
92
* A `ResultConstructor{E, Err}` can be converted to `Result{T, E2}` if `E <: E2`.
82
93
83
-
The first rule merely state that a `Result` can be converted to another `Result` if both the success parameter (`Ok{T}`) and the error parameter (`Err{E}`) error types are a supertype. It is intentionally NOT possible to e.g. convert a `Result{Int, String}` containing an `Ok` to a `Result{Int, Int}`, even if the `Ok` value contains an `Int` which is allowed in both of the `Result` types. The reason for this is that if it was allowed, whether or not conversions threw errors would depend on the _value_ of an error type, not the type. This type-unstable behaviour would defeat idea behind this package, namely to present edge cases as types, not values.
94
+
The first rule merely state that a `Result` can be converted to another `Result` if both the success parameter (`Ok{T}`) and the error parameter (`Err{E}`) error types are a supertype.
95
+
It is intentionally NOT possible to e.g. convert a `Result{Int, String}` containing an `Ok` to a `Result{Int, Int}`, even if the `Ok` value contains an `Int` which is allowed in both of the `Result` types.
96
+
The reason for this is that if it was allowed, whether or not conversions threw errors would depend on the _value_ of an error type, not the type.
97
+
This type-unstable behaviour would defeat idea behind this package, namely to present edge cases as types, not values.
84
98
85
99
The next two rules state that `ResultConstructors` have relaxed this requirement, and so a `ResultConstructors` constructed from an `Ok` or `Err` can be converted if only the `Ok{T}` or the `Err{E}` parameter, respectively, is a supertype, not necessarily both parameters.
100
+
This is what enables use of `Ok(x)`, `Err(x)` and `none` as return values when the function is annotated with return type.
86
101
87
102
There is one last type, `ResultConstructor{T, Union{}}`, which is even more flexible in how it converts. This is created by the `@?` macro, discussed next.
However, if `x` evaluates to an error value `Err{E}`, the macro creates a `ResultConstructor{E, Union{}}`, let's call it `y`, and evaluates to `return y`. In this manner, the macro means "unwrap the value if possible, and else immediately return it to the outer function". `ResultConstructor{E, Union{}}` are even more flexible in what they can be converted to: They can convert to any `Option` type, or any `Result{T, E2}` where `E <: E2`. This allows you to propagate errors from functions returning `Result` to those returning `Option`.
112
+
However, if `x` evaluates to an error value `Err{E}`, the macro creates a `ResultConstructor{E, Union{}}`, let's call it `y`, and evaluates to `return y`.
113
+
In this manner, the macro means "unwrap the value if possible, and else immediately return it to the outer function".
114
+
`ResultConstructor{E, Union{}}` are even more flexible in what they can be converted to:
115
+
They can convert to any `Option` type, or any `Result{T, E2}` where `E <: E2`.
116
+
This allows you to propagate errors from functions returning `Result` to those returning `Option`.
98
117
99
118
Let's see it in action. Suppose you want to implement a safe version of the harmonic mean function, which in turn uses a safe version of `div`:
100
119
@@ -126,6 +145,8 @@ function harmonic_mean(v::AbstractArray{<:Integer})::Option{Float64}
126
145
end
127
146
```
128
147
148
+
In case any of the calls to `safe_div` yields a `none(Float64)`, the `@?` macro evaluates to code equivalent to `return ResultConstructor{Nothing, Union{}}(nothing)`. This value is then converted by the typeassert in the outer function to `none(Float64)`
149
+
129
150
### When to use an error type vs throw an error
130
151
The error handling mechanism provided by ErrorTypes is a distinct method from throwing and catching errors. None is superior to the other in all circumstances.
0 commit comments