Skip to content

Commit

Permalink
Improve error types in the standard library (#1734)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamrecursion committed Jun 24, 2021
1 parent 4975789 commit 620c2eb
Show file tree
Hide file tree
Showing 22 changed files with 359 additions and 139 deletions.
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
Loading

0 comments on commit 620c2eb

Please sign in to comment.