-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0ed2054
commit 4b5145c
Showing
1 changed file
with
81 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,127 @@ | ||
app [main!] { | ||
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", | ||
cli: platform "../../../basic-cli/platform/main.roc", | ||
} | ||
|
||
import pf.Stdout | ||
import cli.Stdout | ||
|
||
Person : { first_name : Str, last_name : Str, birth_year : U16 } | ||
|
||
## This function parses strings like "{FirstName} {LastName} was born in {Year}" | ||
## and if successful returns `Ok {firstName, lastName, birthYear}`. Otherwise | ||
## it returns an `Err` containing a descriptive tag. | ||
## This is the most verbose version, we will do better below. | ||
parse_verbose : Str -> Result Person [InvalidRecordFormat, InvalidNameFormat, InvalidBirthYearFormat] | ||
parse_verbose = \line -> | ||
when line |> Str.splitFirst " was born in " is | ||
Ok { before: full_name, after: birth_year_str } -> | ||
when full_name |> Str.splitFirst " " is | ||
Ok { before: first_name, after: last_name } -> | ||
when Str.toU16 birth_year_str is | ||
Ok birth_year -> | ||
Ok { first_name, last_name, birth_year } | ||
when line |> Str.split_first(" was born in ") is | ||
Ok({ before: full_name, after: birth_year_str }) -> | ||
when full_name |> Str.split_first(" ") is | ||
Ok({ before: first_name, after: last_name }) -> | ||
when Str.to_u16(birth_year_str) is | ||
Ok(birth_year) -> | ||
Ok({ first_name, last_name, birth_year }) | ||
Err _ -> Err InvalidBirthYearFormat | ||
Err(_) -> Err(InvalidBirthYearFormat) | ||
_ -> Err InvalidNameFormat | ||
_ -> Err(InvalidNameFormat) | ||
_ -> Err InvalidRecordFormat | ||
_ -> Err(InvalidRecordFormat) | ||
## Here's a very slightly shorter version using `Result.try` to chain multiple | ||
## functions that each could return an error. It's a bit nicer, don't you think? | ||
## Note: this version returns "raw" errors (`Err NotFound` or `Err InvalidNumStr`). | ||
parse_with_try : Str -> Result Person [InvalidNumStr, NotFound] | ||
parse_with_try = \line -> | ||
line | ||
|> Str.splitFirst " was born in " | ||
|> Result.try \{ before: full_name, after: birth_year_str } -> | ||
full_name | ||
|> Str.splitFirst " " | ||
|> Result.try \{ before: first_name, after: last_name } -> | ||
Str.toU16 birth_year_str | ||
|> Result.try \birth_year -> | ||
Ok { first_name, last_name, birth_year } | ||
|> Str.split_first(" was born in ") | ||
|> Result.try( | ||
\{ before: full_name, after: birth_year_str } -> | ||
full_name | ||
|> Str.split_first(" ") | ||
|> Result.try( | ||
\{ before: first_name, after: last_name } -> | ||
Str.to_u16(birth_year_str) | ||
|> Result.try( | ||
\birth_year -> | ||
Ok({ first_name, last_name, birth_year }), | ||
), | ||
), | ||
) | ||
## This version is like `parseWithTry`, except it uses `Result.mapErr` | ||
## to return more informative errors, just like the ones in `parseVerbose`. | ||
## to return more informative errors, just like the ones in `parse_verbose`. | ||
parse_with_try_v2 : Str -> Result Person [InvalidRecordFormat, InvalidNameFormat, InvalidBirthYearFormat] | ||
parse_with_try_v2 = \line -> | ||
line | ||
|> Str.splitFirst " was born in " | ||
|> Result.mapErr \_ -> Err InvalidRecordFormat | ||
|> Result.try \{ before: full_name, after: birth_year_str } -> | ||
full_name | ||
|> Str.splitFirst " " | ||
|> Result.mapErr \_ -> Err InvalidNameFormat | ||
|> Result.try \{ before: first_name, after: last_name } -> | ||
Str.toU16 birth_year_str | ||
|> Result.mapErr \_ -> Err InvalidBirthYearFormat | ||
|> Result.try \birth_year -> | ||
Ok { first_name, last_name, birth_year } | ||
|> Str.split_first(" was born in ") | ||
|> Result.map_err(\_ -> InvalidRecordFormat) | ||
|> Result.try( | ||
\{ before: full_name, after: birth_year_str } -> | ||
full_name | ||
|> Str.split_first(" ") | ||
|> Result.map_err(\_ -> InvalidNameFormat) | ||
|> Result.try( | ||
\{ before: first_name, after: last_name } -> | ||
Str.to_u16(birth_year_str) | ||
|> Result.map_err(\_ -> InvalidBirthYearFormat) | ||
|> Result.try( | ||
\birth_year -> | ||
Ok({ first_name, last_name, birth_year }), | ||
), | ||
), | ||
) | ||
## The `?` operator, called the "try operator", is | ||
## [syntactic sugar](en.wikipedia.org/wiki/Syntactic_sugar) for `Result.try`. | ||
## It makes the code much less nested and easier to read. | ||
## The following function is equivalent to `parseWithTry`: | ||
parse_with_try_op : Str -> Result Person [NotFound, InvalidNumStr] | ||
parse_with_try_op = \line -> | ||
{ before: full_name, after: birth_year_str } = Str.splitFirst? line " was born in " | ||
{ before: first_name, after: last_name } = Str.splitFirst? full_name " " | ||
birth_year = Str.toU16? birth_year_str | ||
Ok { first_name, last_name, birth_year } | ||
{ before: full_name, after: birth_year_str } = Str.split_first(line, " was born in ")? | ||
{ before: first_name, after: last_name } = Str.split_first(full_name, " ")? | ||
birth_year = Str.to_u16(birth_year_str)? | ||
Ok({ first_name, last_name, birth_year }) | ||
## And lastly the following function is equivalent to `parseWithTryV2`. | ||
## Note that the `?` operator has moved from `splitFirst` & `toU16` to `mapErr`: | ||
parse_with_try_op_v2 : Str -> Result Person [InvalidRecordFormat, InvalidNameFormat, InvalidBirthYearFormat] | ||
parse_with_try_op_v2 = \line -> | ||
{ before: full_name, after: birth_year_str } = | ||
line | ||
|> Str.splitFirst " was born in " | ||
|> Result.mapErr? \_ -> Err InvalidRecordFormat | ||
(Str.split_first(line, " was born in ") |> Result.map_err(\_ -> InvalidRecordFormat))? | ||
{ before: first_name, after: last_name } = | ||
full_name | ||
|> Str.splitFirst " " | ||
|> Result.mapErr? \_ -> Err InvalidNameFormat | ||
birth_year = | ||
Str.toU16 birth_year_str | ||
|> Result.mapErr? \_ -> Err InvalidBirthYearFormat | ||
Ok { first_name, last_name, birth_year } | ||
(Str.split_first(full_name, " ") |> Result.map_err(\_ -> InvalidNameFormat))? | ||
birth_year = Result.map_err(Str.to_u16(birth_year_str), \_ -> InvalidBirthYearFormat)? | ||
Ok({ first_name, last_name, birth_year }) | ||
## This function parses a string using a given parser and returns a string to | ||
## display to the user. Note how we can handle errors individually or in bulk. | ||
parse = \line, parser -> | ||
when parser line is | ||
Ok { first_name, last_name, birth_year } -> | ||
when parser(line) is | ||
Ok({ first_name, last_name, birth_year }) -> | ||
""" | ||
Name: $(last_name), $(first_name) | ||
Born: $(birth_year |> Num.toStr) | ||
Born: $(Num.to_str(birth_year)) | ||
|
||
""" | ||
Err InvalidNameFormat -> "What kind of a name is this?" | ||
Err InvalidBirthYearFormat -> "That birth year looks fishy." | ||
Err InvalidRecordFormat -> "Oh wow, that's a weird looking record!" | ||
Err(InvalidNameFormat) -> "What kind of a name is this?" | ||
Err(InvalidBirthYearFormat) -> "That birth year looks fishy." | ||
Err(InvalidRecordFormat) -> "Oh wow, that's a weird looking record!" | ||
_ -> "Something unexpected happened" # Err NotFound or Err InvalidNumStr | ||
main! = \_args -> | ||
try Stdout.line! (parse "George Harrison was born in 1943" parse_verbose) | ||
try Stdout.line! (parse "John Lennon was born in 1940" parse_with_try) | ||
try Stdout.line! (parse "Paul McCartney was born in 1942" parse_with_try_v2) | ||
try Stdout.line! (parse "Ringo Starr was born in 1940" parse_with_try_op) | ||
try Stdout.line! (parse "Stuart Sutcliffe was born in 1940" parse_with_try_op_v2) | ||
Stdout.line!(parse("George Harrison was born in 1943", parse_verbose))? | ||
Stdout.line!(parse("John Lennon was born in 1940", parse_with_try))? | ||
Stdout.line!(parse("Paul McCartney was born in 1942", parse_with_try_v2))? | ||
Stdout.line!(parse("Ringo Starr was born in 1940", parse_with_try_op))? | ||
Stdout.line!(parse("Stuart Sutcliffe was born in 1940", parse_with_try_op_v2))? | ||
Ok({}) | ||
Ok {} | ||
expect parse("George Harrison was born in 1943", parse_verbose) == "Name: Harrison, George\nBorn: 1943\n" | ||
expect parse("John Lennon was born in 1940", parse_with_try) == "Name: Lennon, John\nBorn: 1940\n" | ||
expect parse("Paul McCartney was born in 1942", parse_with_try_v2) == "Name: McCartney, Paul\nBorn: 1942\n" | ||
expect parse("Ringo Starr was born in 1940", parse_with_try_op) == "Name: Starr, Ringo\nBorn: 1940\n" | ||
expect parse("Stuart Sutcliffe was born in 1940", parse_with_try_op_v2) == "Name: Sutcliffe, Stuart\nBorn: 1940\n" |