# Tutorial

Learn Par in 10 short programs.

This tutorial assumes prior programming experience. Ideally, you've been writing code for a few years, and you're comfortable with at least one statically-typed language.

## 1. Basics

Add two numbers and print the sum.

• Line 1 — All Par programs begin with a module declaration. A module is simply a namespace that can contain functions, constants, types, etc.

• Line 3 — We define an `add()` function that accepts two parameters, `x` and `y`, and returns their sum. All standard arithmetic and comparison operators (`+`, `-`, `*`, `/`, `==`, `>`, `<`, etc.) work in Par. You can find all possible expressions in the reference manual.

• Lines 5–7 — A program begins by executing the `main()` function, which accepts no arguments. In our `main()`, we call `add(3, 4)` and use `let` to assign the result to the variable `sum`. We then call the builtin function `print()`, which will print `sum` to `stdout`.

• Note: You can never re-assign or modify a variable; you can only introduce a new binding via `let`. This immutability makes it easy to reason about your code, but takes time to get used to.

File `basics.par`:

 ```1 2 3 4 5 6 7 ``` ```module Basics add(x, y) = x + y main() = let sum = add(3, 4) print(sum) ```
```# compile and run
\$ par basics.par
7
```

## 2. Price checker

Check if we can buy a phone with \$300.

• Lines 3, 11, 14 — We provide type signatures for each global constant/function definition. Although not required, this is good practice; it ensures the type checker affirms our understanding of the code, and prevents unexpected types from propagating. You can find all possible types in the reference manual.

• Lines 3–9 — We define a global hash map, `prices`, that maps item names to item prices. Its type is `Map<String, Float>`, meaning there are `String` keys and `Float` values. Note that because all data structures are immutable, it's implied that prices is a constant and cannot be changed.

• Lines 11–12 — The type signature for `can_buy?()` indicates that it accepts two parameters, a `String` and a `Float`, and then returns a `Bool`. `can_buy?()` uses the builtin function `get()` to find the value associated with the key `item` in the `prices` map. It then returns whether the resulting price is within the provided `budget`.

• Line 14`main : () -> ()` means that `main()` accepts no arguments, and returns a value of type unit, or `()`. The unit type `()` is used to indicate that there's no useful return value; that is, this function effectively returns nothing.

File `price-checker.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` ```module PriceChecker prices : Map prices = { "Candy" => 0.25 "Phone" => 599.99 "Coffee" => 4.50 "Juice" => 9 } can_buy? : (String, Float) -> Bool can_buy?(item, budget) = get(prices, item) <= budget main : () -> () main() = print(can_buy?("Phone", 300)) ```
```# compile and run
\$ par price-checker.par
false
```

## 3. Digitizer

Convert a number into an array of its digits.

• Line 3 — The type `[Int]` means a list of integers. `to_digits()` accepts two parameters: `Int` and `[Int]`, and returns `[Int]`. The `[Int]` passed into `to_digits()`, `accum`, is the accumulated list of digits so far. `to_digits()` is defined recursively; in the first call, `accum` will be the empty list, `[]`, and in recursive calls, it'll contain digits.

• Lines 6, 9 — The expression `[h | t]` returns a new list with the first element `h` followed by all elements in the existing list `t`. `h` is called the head of the new list, and `t` is the tail. This is an `O(1)` operation and is the most efficient way to put elements into a list. Appending an element at the end of a list, by contrast, copies all prior elements, making it `O(n)`.

• Line 8`%` is the remainder operator. `num % 10` finds the remainder when `num` is divided by `10`, giving us the last digit.

• Line 9`trunc()` converts a `Float` to an `Int` by truncating anything after the decimal point. So ```trunc(num / 10)``` returns `num` with the last digit removed. e.g. ```trunc(137 / 10) == trunc(13.7) == 13```.

• Line 12`to_digits(137, [])` calls `to_digits(13, )`, which calls `to_digits(1, [3, 7])`, which returns `[1, 3, 7]`.

File `digitizer.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 ``` ```module Digitizer to_digits : (Int, [Int]) -> [Int] to_digits(num, accum) = if num < 10 then [num | accum] else let digit = num % 10 to_digits(trunc(num / 10), [digit | accum]) main : () -> () main() = print(to_digits(137, [])) ```
```# compile and run
\$ par digitizer.par
[1, 3, 7]
```

## 4. Undigitizer

Convert an array of digits back into a number.

• Line 3`to_num()` accepts a list of integers, `[Int]` as the sole parameter and returns an `Int`.

• Lines 4–8 — The `match` keyword lets us pattern match the list of digits. It goes through each case `pat => expr` on lines 5-7 in order. If the pattern `pat` matches `digits`, then `expr` will be executed and returned; otherwise, `match` will try the next case. You can find all possible patterns in the reference manual.

• Line 5 — If `digits` is precisely an empty list, `[]`, then we return 0 as the corresponding number.

• Line 6 — The pattern `[d]` checks if `digits` has exactly one element, which we'll call `d`. `d` is accessible to the right-hand side expression, and we simply return `d` as the corresponding number.

• Line 7 — The pattern `[d1, d2 | t]` checks if `digits` has at least two elements `d1` and `d2`, followed by any remaining elements in the tail list `t`. `t` may be empty. In this case, we combine `d1` and `d2` into one number, prepend it to `t`, and recursively call ourselves.

• Line 11`to_num([1, 3, 7])` calls `to_num([13, 7])`, which calls `to_num()`, which returns `137`.

File `undigitizer.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 ``` ```module Undigitizer to_num : [Int] -> Int to_num(digits) = match digits { [] => 0 [d] => d [d1, d2 | t] => to_num([d1 * 10 + d2 | t]) } main : () -> () main() = print(to_num([1, 3, 7])) ```
```# compile and run
\$ par undigitizer.par
137
```

## 5. Word analyzer

Find the number of consonants and vowels in a given word.

• Line 3–4`vowels_set` is a hash set containing characters, or a `Set<Char>`. A literal set is created with the `#[elem1, elem2, ...]` syntax. A character is denoted by single quotes.

• Line 6`analyze()` accepts a `String` word argument and returns a tuple containing two integers, `(Int, Int)`.

• Line 8`String` is a standard library module that contains a function `to_chars()`, which converts a `String` to a list of characters, `[Char]`.

• Line 10, 14 — The builtin `filter()` accepts two arguments: a function that, given an element, returns a `Bool` indicating whether to keep that element, and a collection of elements, which could be a `List`, `Map`, or `Set`. It returns a new collection containing only the elements for which the function returns `true`.

• Line 12 — The syntax `|arg1, arg2, ...| expr` creates an anonymous function that accepts arguments `arg1`, `arg2`, etc. and executes the given `expr` expression. In this case, our function accepts a character `ch` and returns `true` if `ch` isn't a vowel. You can refer to the documentation for `contains?()`.

• Line 16 — The syntax `contains?(vowels_set, _)` creates a new function that accepts one argument, which we'll call `ch`, and returns `contains?(vowels_set, ch)`. This is a shorthand for `|ch| contains?(vowels_set, ch)`.

• Line 19`(a, b)` is the syntax for creating a tuple. A tuple may contain two or more elements.

• Line 23 — The left-hand side of a `let` binding can be any valid pattern, as per the reference manual. In this case, we're matching a tuple that has two elements.

• Line 25, 27`++` is used to concatenate two strings, lists, sets, or maps. `to_pretty()` converts any type into a pretty `String`.

File `word-analyzer.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ``` ```module WordAnalyzer vowels_set : Set vowels_set = #['a', 'e', 'i', 'o', 'u'] analyze : String -> (Int, Int) analyze(word) = let chars = String.to_chars(word) let consonants = filter( chars, |ch| !contains?(vowels_set, ch) ) let vowels = filter( chars, contains?(vowels_set, _) ) (length(consonants), length(vowels)) main : () -> () main() = let (num_consonants, num_vowels) = analyze("arithmetic") print("Num consonants: " ++ to_pretty(num_consonants)) print("Num vowels: " ++ to_pretty(num_vowels)) ```
```# compile and run
\$ par word-analyzer.par
Num consonants: 6
Num vowels: 4
```

## 6. People

Identify people who are tall and print their names.

• Lines 3–7`A` and `B` are type variables, which represent any two types. For any types `A` and `B`, `map_list()` requires two arguments:

• A list containing elements of type `A`.
• A function that accepts some value of type `A` and returns some value of type `B`.

`map_list()` returns a new list containg elements of type `B`. It does so by applying the given function to each element of the given list, creating a new list of mapped elements.

`map_list()` works for any types `A` and `B`, and is said to be polymorphic over `A` and `B`. For example, in ```map_list([1.5, 2.7, 3], |x| x < 2)```, `A` is `Float` and `B` is `Bool`, with the result ```[true, false, false]```.

• Lines 9–14`people` is a list of anonymous records. A record is a collection of fields, each with a name and associated value. In this case, each record has two fields: `name`, whose value is a `String`, and `height`, whose value is a `Float`.

• Lines 19, 20 — The pipe operator `|>` takes the left-hand side and makes it the first argument of the right-hand side. `a |> f(b)` is equivalent to `f(a, b)`. `p.height` and `p.name` access the `height` and `name` fields, respectively, of the records created on lines 11-13.

• Note: The builtin function `map()` does exactly what `map_list` does, but it also works for sets and maps. We're implementing `map_list` here for the sake of learning.

File `people.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ``` ```module People map_list : ([A], A -> B) -> [B] map_list(l, f) = match l { [h | t] => [f(h) | map_list(t, f)] [] => [] } people : [{ name : String, height : Float }] people = [ { name = "Jill", height = 70.6 } { name = "Bob", height = 68 } { name = "Laura", height = 73.25 } ] main : () -> () main() = let tall_names = people |> filter(|p| p.height > 70) |> map_list(|p| p.name) print(tall_names) ```
```# compile and run
\$ par people.par
["Jill", "Laura"]
```

Model an HTML document and find links within in.

• Lines 3–12 — An enum is a sum type, meaning it models data that comes in different forms. Each form is called a variant, and it represents one case that the data might fall in. For instance, if we're modeling an arithmetic expression, we could have an addition operation, an exponentiation, or a negation; these are three different variants. Every variant has a name and, optionally, one or more arguments that help describe it.

• Lines 4–5 — The variants `Href` and `Src` are two different HTML attributes—two cases— that we're modeling in our `Attribute` sum type. Neither take arguments.

• Lines 9–11`Html` represents an HTML Document, which is composed of tags and text. A `Tag()` is described by three arguments: its name, like `"div"` or `"body"`, its attributes, a hash map from `Attribute` keys to `String` values, and its contents, `Html` (notice the recursive nature of this type). `Text()` is described by just a `String`. Finally, since we can have multiple tags and text side-by-side, `Siblings()` is described by an array of `Html` values.

• Lines 14–25 — Using the `Attribute` and `Html` enums we just declared, we model the following HTML document:

```<a href="http://par-lang.com">Par</a>
<div>
<a href="/learn">Learn Par</a>
<img src="par.png" />
</div>
```

`Tag()`, `Text()`, and `Siblings()` are functions that accept their corresponding arguments, whereas `Href`/`Src` are constants.

• Line 27`find_links()` recursively searches through `Html` for `"a"` tags with `Href` attributes, which represent hyperlinks. It returns a list of those hyperlinks, so for the document above, we'd get `["http://par-lang.com", "/learn"]`.

• Line 29`find_links()` uses pattern matching to handle different cases of `Html`. The pattern `Text(_)` matches the `Text` variant. The `_` is a way of ignoring what the `String` argument is; we don't care because we know text isn't a link, so we return an empty list.

• Lines 31–32 — The pattern `Tag("a", attrs, content)` matches `<a>` tags. We assign the names `attrs` and `content` to the attributes and contents of the tag. We then refer to these in the right-hand side of the `=>`, where we get the value of the `Href` attribute, and continue finding links in the `content.`

• Lines 35–36`List.flat_map()` will call `find_links()` for each sibling, and then concatenate the results into one list.

File `link-finder.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ``` ```module LinkFinder enum Attribute { Href Src } enum Html { Tag(String, Map, Html) Text(String) Siblings([Html]) } doc : Html doc = Siblings([ Tag( "a", { Href => "http://par-lang.com" }, Text("Par") ) Tag("div", {}, Siblings([ Tag("a", { Href => "/learn" }, Text("Learn Par")) Tag("img", { Src => "par.png" }, Text("")) ])) ]) find_links : Html -> [String] find_links(html) = match html { Text(_) => [] Tag("a", attrs, content) => [get(attrs, Href) | find_links(content)] Tag(_, _, content) => find_links(content) Siblings(siblings) => List.flat_map(siblings, find_links) } main : () -> () main() = print(find_links(doc)) ```
```# compile and run
["http://par-lang.com", "/learn"]
```

## 8. Validator

Validates a user's post and fixes any issues.

• Lines 3–6 — A struct is very similar to an anonymous record, but it's a named type. It contains a set of fields, each with a name and an associated value. We define a struct by specifying each field name and the associated value's type.

• Lines 8–9 — An exception is an error condition that we can raise in our program to halt execution. We can also catch exceptions and respond to them. Here, we define two exceptions. Note that each exception is defined in the same way as an enum variant: it has a name and, optionally, arguments to describe it. Exceptions have type `Exception`.

• Lines 13, 15 — We access struct fields in the same way as anonymous record fields, using the `.` operator: `p.title` and `p.likes`.

• Lines 14, 16 — We raise an exception using the `raise` keyword. Exceptions with arguments act as functions, whereas those without act as constants, just like enum variants.

• Lines 20–25 — We declare an array of `Post` objects. Each `Post` looks almost exactly like an anonymous record, except that we specify the type `Post` prior to the `{`.

• Lines 30–33 — The `try ... catch { ... }` syntax lets us catch exceptions and respond to them. After `try` follows the expression we want to monitor for exceptions. The `catch` block is very similar to a `match` block, where we pattern match different exceptions.

• Line 31 — The syntax `{ title = "untitled" | p }` creates a new `Post` that has all the same fields as `p`, but with the `title` set to `"untitled"`. So if the exception matches `EmptyTitle`, we set a title on the post to fix it. This syntax also works with anonymous records.

• Line 32 — If the exception is `NegativeLikes()` with a like count `l`, we update the post's like count to `-l` to make it positive.

File `validator.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ``` ```module Validator struct Post { likes : Int title : String } exception EmptyTitle exception NegativeLikes(Int) check_post : Post -> Post check_post(p) = if p.title == "" then raise EmptyTitle else if p.likes < 0 then raise NegativeLikes(p.likes) else p posts : [Post] posts = [ Post { likes = 0, title = "My first post!" } Post { likes = -37, title = "Boring" } Post { likes = 15, title = "" } ] main : () -> () main() = let fixed_posts = map(posts, |p| try check_post(p) catch { EmptyTitle => { title = "untitled" | p } NegativeLikes(l) => { likes = -l | p } } ) print(fixed_posts) ```
```# compile and run
\$ par validator.par
[
Post { likes = 0, title = "My first post!" }
Post { likes = 37, title = "Boring" }
Post { likes = 15, title = "untitled" }
]
```

## 9. Conversion

Convert different types to an integer.

• Lines 3–5 — An interface declares one or more functions that work across many types. `to_integer()` accepts a type `T` and returns `Int`. `T` is a placeholder for any type that implements the `ToInteger` interface. In other words, for any type `T` that implements the `ToInteger` interface, you may call `to_integer()` to get an `Int`.

• Lines 7–9 — We state that the type `Bool` implements the `ToInteger` interface. To satisfy the interface, we provide an implementation for `to_integer()` that accepts a `Bool` and returns an `Int`. We can now call `to_integer()` on any `Bool` value and get back an `Int`.

• Lines 11–13 — We repeat the above, except for `Float`, calling `trunc()` to truncate a `Float` into an `Int`. We can now call `to_integer()` on any `Bool` or `Float` value to get an `Int`!

• Lines 15–16`zero?()` accepts a value of type ```A ~ ToInteger```, meaning any type `A` such that `A` satisfies the `ToInteger` interface, and returns a `Bool`. It calls `to_integer()` on the argument, and checks if the result is `0`.

• Lines 20–21 — We can call `to_integer()` and `zero?()` with either a `Float` or `Bool` thanks to our interface! Both functions are said to be polymorphic over the `Float` and `Bool` types.

File `conversion.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ``` ```module Conversion interface ToInteger { to_integer : T -> Int } impl ToInteger for Bool { to_integer(b) = if b then 1 else 0 } impl ToInteger for Float { to_integer(f) = trunc(f) } zero? : A ~ ToInteger -> Bool zero?(x) = to_integer(x) == 0 main : () -> () main() = let float_results = (to_integer(3.7), zero?(3.7)) let bool_results = (to_integer(false), zero?(false)) print(float_results) print(bool_results) ```
```# compile and run
\$ par conversion.par
(3, false)
(0, true)
```

## 10. Importer

Split code into multiple files and import between them.

• Line 9 — The `export` keyword lets us expose functions/constants like `raw_data` to other modules, allowing them to import and use it. Enums and structs are automatically exported.

File `csv.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 ``` ```module Csv enum Row { Company(String, Int) Person(String) } raw_data : String export raw_data = "Google,1998\n" ++ "Larry Page\n" ++ "Apple,1976\n" ++ "Steve Jobs\n" ```
• Line 3 — We can import a module by specifying the path to it, either relative to the current file, or absolute. Since `csv.par` is in the same directory as `parser.par`, our relative path is simply `"./csv"` (the extension is implied). This allows us to access all exported fields of the `Csv` module by their full qualified name, like `Csv.raw_data`.

• Line 4 — Standard library modules like `String`, `List`, `Set`, etc. are imported by default, but we can also directly import any names so we can access them unqualified. In this case, the `(*)` indicates we're directly importing all names from `String`. Hence, functions like `lines()` and `split()` are accessible without the `String.` prefix.

• Lines 6, 8, 10, 11 — We access exported types, variants, and constants in the `Csv` module by their full qualified name.

• Lines 8, 9 — We access `lines()` and `split()` from the `String` module without the `String.` prefix.

• Line 12 — The compiler will force us to handle all cases when we perform a pattern match. A list may contain more than 2 elements or fewer than 1, so we must add a catch-all case here. We call `fail()` to generate a runtime exception.

File `reader.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 ``` ```module Reader import "./csv" import String (*) entities : [Csv.Row] export entities = let csv_lines = lines(Csv.raw_data) map(csv_lines, |line| match split(line, ",") { [name, year] => Csv.Company(name, to_int(year)) [name] => Csv.Person(name) _ => fail("Unexpected line: " ++ line) }) ```
• Line 3 - `(entities)` means we directly import `entities` from `parser.par`. This way, we can access it unqualified as `entities` and not `Parser.entities`.

• Line 4 - `(Row, variants Row)` means we directly import the `Row` type and all variants of the `Row` type, which are `Person()` and `Company()`. We can access all three of these unqualified. If we'd like to add more direct imports, we can comma separate them in these parens.

File `formatter.par`:

 ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` ```module Formatter import "./reader" (entities) import "./csv" (Row, variants Row) format_row : Row -> String format_row(r) = match r { Company(name, year) => "Company: " ++ name ++ ", founded in " ++ to_str(year) Person(name) => "Person: " ++ name } main : () -> () main() = map(entities, format_row) |> print ```
```# compile and run
\$ par formatter.par
[