Skip to content

Commit a272e65

Browse files
committed
Improve explanations in docs
1 parent 6c8c657 commit a272e65

File tree

2 files changed

+34
-11
lines changed

2 files changed

+34
-11
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function second_csv_field(s::Union{String, SubString{String}})::Option{SubString
1717
p = nothing
1818
for i in 1:ncodeunits(s)
1919
if codeunit(s, i) == UInt8(',')
20-
isnothing(p) || return some(SubString(s, p, i-1))
20+
isnothing(p) || return some(SubString(s, p, prevind(s, i)))
2121
p = i + 1
2222
end
2323
end
@@ -44,7 +44,9 @@ You can of course mix and match regular exceptions and error types. That is usef
4444
```julia
4545
function first_field(io::IO)::Option{String}
4646
lines = eachline(io)
47-
@assert startswith(first(iterate(lines)), "#Name\t")
47+
if! startswith(first(iterate(lines)), "#Name\t")
48+
error("In file, expected header to begin with: \"#Name\\t\"")
49+
end
4850
nextit = iterate(lines)
4951
isnothing(nextit) && return none
5052
line = first(nextit)

docs/src/usage.md

+30-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Usage
22

33
### 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}`.
76

7+
You can construct it like this:
88
```
99
julia> Result{String, Int}(Ok("Nothing went wrong"))
1010
Result{String, Int64}(Ok("Nothing went wrong"))
@@ -31,7 +31,10 @@ Option{Integer}(some(1))
3131
```
3232

3333
#### 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`:
3538

3639
```
3740
julia> Err(1)
@@ -41,7 +44,8 @@ julia> Ok(1)
4144
ErrorTypes.ResultConstructor{Int64, Ok}(1)
4245
```
4346

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.
4549

4650
`none` by itself is a constant for `ErrorTypes.ResultConstructor{Nothing, Err}(nothing)` - we will come back to why this is particularly convenient.
4751

@@ -58,7 +62,11 @@ And not this:
5862
invert(x::Integer) = iszero(x) ? none(Float64) : some(1/x)
5963
```
6064

61-
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:
6270

6371
```
6472
function get_length(x)::Result{Int, Base.IteratorSize}
@@ -71,7 +79,10 @@ function get_length(x)::Result{Int, Base.IteratorSize}
7179
end
7280
```
7381

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`.
7586

7687
### Conversion rules
7788
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
8091
* A `ResultConstructor{T, Ok}` can be converted to `Result{T2, E}` if `T <: T2`.
8192
* A `ResultConstructor{E, Err}` can be converted to `Result{T, E2}` if `E <: E2`.
8293

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.
8498

8599
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.
86101

87102
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.
88103

@@ -94,7 +109,11 @@ julia> @? Result{String, Int}(Ok("foo"))
94109
"foo"
95110
```
96111

97-
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`.
98117

99118
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`:
100119

@@ -126,6 +145,8 @@ function harmonic_mean(v::AbstractArray{<:Integer})::Option{Float64}
126145
end
127146
```
128147

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+
129150
### When to use an error type vs throw an error
130151
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.
131152

0 commit comments

Comments
 (0)