Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do Errors Better #1734

Merged
merged 12 commits into from
May 11, 2021
3 changes: 3 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
- Removed reflective access when loading the OpenCV library
([#1727](https://github.com/enso-org/enso/pull/1727)). Illegal reflective
access operations were deprecated and will be denied in future JVM releases.
- Overhauled the types we use for errors throughout the standard library
([#1734](https://github.com/enso-org/enso/pull/1734)). They are now much more
informative, and should provide more clarity when things go wrong.

## Miscellaneous

Expand Down
22 changes: 19 additions & 3 deletions distribution/std-lib/Standard/src/Base/Data/Json.enso
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ type Parse_Error message

Converts the error to a display representation.
Parse_Error.to_display_text : Text
Parse_Error.to_display_text = "Parse error in parsing JSON: " + this.message.to_text + "."
Parse_Error.to_display_text =
"Parse error in parsing JSON: " + this.message.to_text + "."

## Gets the value associated with the given key in this object.

Expand All @@ -169,8 +170,22 @@ Parse_Error.to_display_text = "Parse error in parsing JSON: " + this.message.to_
import Standard.Examples

example_get = Examples.json_object.get "title"
Object.get : Text -> Json ! Nothing
Object.get field = this.fields.get field
Object.get : Text -> Json ! No_Such_Field_Error
Object.get field = this.fields.get field . map_error case _ of
Map.No_Value_For_Key_Error _ -> No_Such_Field_Error field
x -> x

## UNSTABLE

An error indicating that there is no such field in the JSON object.
type No_Such_Field_Error field_name

## UNSTABLE

Pretty prints the no such field error.
No_Such_Field_Error.to_display_text : Text
No_Such_Field_Error.to_display_text =
"The field " + this.field_name.to_text + " is not present in this object."

## UNSTABLE

Expand Down Expand Up @@ -267,3 +282,4 @@ Base.Boolean.to_json = Boolean this
Nothing.to_json
Nothing.to_json : Null
Nothing.to_json = Null

32 changes: 22 additions & 10 deletions distribution/std-lib/Standard/src/Base/Data/List.enso
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ type List
import Standard.Examples

example_head = Examples.list.head
head : Any ! Nothing
head : Any ! Empty_Error
head = case this of
Cons a _ -> a
Nil -> Error.throw Nothing
Nil -> Error.throw Empty_Error

## Get all elements from the list except the first.

Expand All @@ -278,10 +278,10 @@ type List
import Standard.Examples

example_tail = Examples.list.tail
tail : List ! Nothing
tail : List ! Empty_Error
tail = case this of
Cons _ b -> b
Nil -> Error.throw Nothing
Nil -> Error.throw Empty_Error

## Get all elements from the list except the last.

Expand All @@ -291,14 +291,14 @@ type List
import Standard.Examples

example_init = Examples.list.init
init : List ! Nothing
init : List ! Empty_Error
init =
init' x y = case y of
Nil -> Nil
Cons a b -> Cons x (init' a b)
case this of
Cons a b -> init' a b
Nil -> Error.throw Nothing
Nil -> Error.throw Empty_Error

## Get the last element of the list.

Expand All @@ -308,9 +308,9 @@ type List
import Standard.Examples

example_last = Examples.list.last
last : Any | Nothing
last : Any ! Empty_Error
last = case this.fold Nothing (_ -> r -> r) of
Nothing -> Error.throw Nothing
Nothing -> Error.throw Empty_Error
a -> a

## Get the first element from the list.
Expand All @@ -321,7 +321,7 @@ type List
import Standard.Examples

example_first = Examples.list.first
first : Any ! Nothing
first : Any ! Empty_Error
first = this.head

## Get all elements from the list except the first.
Expand All @@ -332,9 +332,20 @@ type List
import Standard.Examples

example_rest = Examples.list.rest
rest : List ! Nothing
rest : List ! Empty_Error
rest = this.tail

## UNSTABLE

An error representing that the list is empty.
type Empty_Error

## UNSTABLE

Pretty prints the empty error.
Empty_Error.to_display_text : Text
Empty_Error.to_display_text = "The List is empty."

## PRIVATE
A helper for the `map` function.

Expand All @@ -346,6 +357,7 @@ type List
Uses unsafe field mutation under the hood, to rewrite `map` in
a tail-recursive manner. The mutation is purely internal and does not leak
to the user-facing API.
map_helper : List -> Any -> (Any -> Any) -> Nothing
map_helper list cons f = case list of
Cons h t ->
res = Cons (f h) Nil
Expand Down
21 changes: 14 additions & 7 deletions distribution/std-lib/Standard/src/Base/Data/Map.enso
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ singleton key value = Bin 1 key value Tip Tip
import Standard.Base.Data.Map.Internal

example_from_vector = Map.from_vector [[1, 2], [3, 4]]
from_vector : Vector.Vector -> Map
from_vector : Vector.Vector Any -> Map
from_vector vec = vec.fold Map.empty (m -> el -> m.insert (el.at 0) (el.at 1))

## A key-value store. This type assumes all keys are pairwise comparable,
Expand Down Expand Up @@ -114,7 +114,7 @@ type Map
import Standard.Examples

example_to_vector = Examples.map.to_vector
to_vector : Vector.Vector
to_vector : Vector.Vector Any
to_vector =
builder = Vector.new_builder
to_vector_with_builder m = case m of
Expand Down Expand Up @@ -177,8 +177,8 @@ type Map
insert : Any -> Any -> Map
insert key value = Internal.insert this key value

## Gets the value associated with `key` in this map, or throws a `Nothing`,
if `key` is not present.
## Gets the value associated with `key` in this map, or throws a
`No_Value_For_Key_Error` if `key` is not present.

Arguments:
- key: The key to look up in the map.
Expand All @@ -190,10 +190,10 @@ type Map
import Standard.Examples

example_get = Examples.map.get 1
get : Any -> Any ! Nothing
get : Any -> Any ! No_Value_For_Key_Error
get key =
go map = case map of
Tip -> Error.throw Nothing
Tip -> Error.throw (No_Value_For_Key_Error key)
Bin _ k v l r ->
if k == key then v else
if k > key then @Tail_Call go l else @Tail_Call go r
Expand Down Expand Up @@ -446,5 +446,12 @@ type Map

Arguments:
- key: The key that was asked for.
type No_Value_For_Key key
type No_Value_For_Key_Error key

## UNSTABLE

Converts the error into a human-readable representation.
No_Value_For_Key_Error.to_display_text : Text
No_Value_For_Key_Error.to_display_text =
"The map contained no value for the key " + this.key.to_text + "."

21 changes: 17 additions & 4 deletions distribution/std-lib/Standard/src/Base/Data/Number/Extensions.enso
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from Standard.Base import all
from Standard.Base import all hiding Parse_Error

polyglot java import java.lang.Double
polyglot java import java.lang.Math
Expand Down Expand Up @@ -231,7 +231,7 @@ Number.to_json : Json.Number
Number.to_json = Json.Number this

## Parses a textual representation of a decimal into a decimal number, returning
`Nothing` if the text does not represent a valid decimal.
a `Parse_Error` if the text does not represent a valid decimal.

Arguments:
- text: The text to parse into a decimal.
Expand All @@ -240,7 +240,20 @@ Number.to_json = Json.Number this
Parse the text "7.6" into a decimal number.

Decimal.parse 7.6
Decimal.parse : Text -> Decimal ! Nothing
Decimal.parse : Text -> Decimal ! Parse_Error
Decimal.parse text =
Panic.recover (Double.parseDouble text) . catch (_ -> Error.throw Nothing)
Panic.recover (Double.parseDouble text) . catch _->
Error.throw (Parse_Error text)

## UNSTABLE

A syntax error when parsing a double.
type Parse_Error text

## UNSTABLE

Pretty print the syntax error.
Parse_Error.to_display_text : Text
Parse_Error.to_display_text =
"Could not parse " + this.text.to_text + " as a double."

3 changes: 2 additions & 1 deletion distribution/std-lib/Standard/src/Base/Data/Time.enso
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,8 @@ type Time
example_format =
Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEE MMM d (HH:mm)"
format : Text -> Text
format pattern = DateTimeFormatter.ofPattern pattern . format this.internal_zoned_date_time
format pattern =
DateTimeFormatter.ofPattern pattern . format this.internal_zoned_date_time

type Time_Error

Expand Down
23 changes: 18 additions & 5 deletions distribution/std-lib/Standard/src/Base/Data/Time/Duration.enso
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ polyglot java import java.time.Period as Java_Period
example_between = Duration.between Time.now (Time.new 2010 10 20)
between : Time -> Time -> Duration
between start_inclusive end_exclusive =
Duration (Java_Period.ofDays 0 . normalized) (Java_Duration.between start_inclusive.internal_zoned_date_time end_exclusive.internal_zoned_date_time)
period = Java_Period.ofDays 0 . normalized
start = start_inclusive.internal_zoned_date_time
end = end_exclusive.internal_zoned_date_time
duration = Java_Duration.between start end
Duration period duration

type Duration

Expand Down Expand Up @@ -52,7 +56,10 @@ type Duration

example_add = 1.month + 12.hours
+ : Duration -> Duration
+ that = Duration (this.internal_period . plus that.internal_period . normalized) (this.internal_duration . plus that.internal_duration)
+ that =
period = this.internal_period . plus that.internal_period . normalized
duration = this.internal_duration . plus that.internal_duration
Duration period duration

## Subtract the specified amount of time from this duration.

Expand All @@ -73,7 +80,10 @@ type Duration

example_subtract = 7.months - 30.minutes
- : Duration -> Duration
- that = Duration (this.internal_period . minus that.internal_period . normalized) (this.internal_duration . minus that.internal_duration)
- that =
period = this.internal_period . minus that.internal_period . normalized
duration = this.internal_duration . minus that.internal_duration
Duration period duration

## Get the portion of the duration expressed in nanoseconds.

Expand Down Expand Up @@ -167,13 +177,16 @@ type Duration
seconds and nanosecnods.

> Example
Convert duration of a year and a hour to a vector returning `[1, 0, 0, 1, 0, 0, 0]`.
Convert duration of a year and a hour to a vector returning
`[1, 0, 0, 1, 0, 0, 0]`.

import Standard.Base.Data.Time.Duration

example_to_vec = (1.year + 1.hour).to_vector

> Example
Convert duration of 800 nanoseconds to a vector returning `[0, 0, 0, 0, 0, 0, 800]`
Convert duration of 800 nanoseconds to a vector returning
`[0, 0, 0, 0, 0, 0, 800]`

import Standard.Base.Data.Time.Duration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) =
import Standard.Base.Data.Time.Time_Of_Day

example_parse = Time_Of_Day.parse "4:30AM" "h:mma"
parse : Text -> Text ! Nothing -> Locale -> Time_Of_Day ! Time.Time_Error
parse : Text -> Text | Nothing -> Locale -> Time_Of_Day ! Time.Time_Error
parse text pattern=Nothing locale=Locale.default =
result = Panic.recover <| case pattern of
Nothing -> LocalTime.parse text
Expand Down