Skip to content

Commit

Permalink
Rearranging Global, Local, and Pattern Matching sections
Browse files Browse the repository at this point in the history
Sabine to create new examples
  • Loading branch information
christinerose authored Dec 1, 2023
1 parent 1d0626e commit 486f5b3
Showing 1 changed file with 70 additions and 82 deletions.
152 changes: 70 additions & 82 deletions data/tutorials/language/0it_00_values_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ In OCaml, an expression's type remains consistent from its initial declaration t

## Global Definitions

Every expression can be named. This is the purpose of the `let … = … ` statement. The name is on the left; the expression is on the right.
Every value can be named. This is the purpose of the `let … = … ` statement. The name is on the left; the expression is on the right.
* If the expression can be evaluated, it takes place right away.
* Otherwise, the expression is turned into a value as-is. That's the case of function definition.

Expand All @@ -81,13 +81,77 @@ val the_answer : int = 42

Global definitions are those entered at the top level. Here, `the_answer` is the global definition with a value of 42.

## Local Definitions

Local definitions are like global definitions, except the name is only bound inside an expression:
```ocaml
# let d = 2 * 3 in d * 7;;
- : int = 42
# d;;
Error: Unbound value d
```

Local definitions are introduced by the `let … = … in …` expression. The name bound before the `in` keyword is only bound in the expression after the `in` keyword. Here, the name `d` is bound to `6` inside the expression `d * 7`.

A couple of remarks:
- No global definition is introduced in this example, which is why we get an error.
- Computation of `2 * 3` will always take place before `d * 7`.

Local definitions can be chained (one after another) or nested (one inside another). Here is an example of chaining:
```ocaml
# let d = 2 * 3 in
let e = d * 7 in
d * e;;
- : int = 252
# d;;
Error: Unbound value d
# e;;
Error: Unbound value e
```

Here is how scoping works:
- `d` is bound to `6` inside `let e = d * 7 in d * e`
- `e` is bound to `42` inside `d * e`

Here is an example of nesting:
```ocaml
# let d =
let e = 2 * 3 in
e * 5 in
d * 7;;
- : int = 210
# d;;
Error: Unbound value d
# e;;
Error: Unbound value e
```
Here is how scoping works:
- `e` is bound to `6` inside `e * 5`
- `d` is bound to `30` inside `d * 7`

Arbitrary combinations of chaining or nesting are allowed.

In both examples, `d` and `e` are local definitions.


## Pattern Matching in Definitions

<!-- 5 cases to illustrate below: Unit case. `_`, tuples, user-defined constructor variants, and records-->
<!-- FIXME: review & revise this entire section :: Sabine -->

<!--the example illustrates tuples::-->
When a variant type has a single constructor, it is possible to combine pattern matching and definitions. The pattern is written between the `let` keyword and the equal sign. A very common case is pairs. It allows the creation of two names with a single `let`.
```ocaml
# let (x, y) = List.split [(1, 2); (3, 4); (5, 6); (7, 8)];;
val x : int list = [1; 3; 5; 7]
val y : int list = [2; 4; 6; 8]
```

<!-- user-defined single constructor variant example -->
<!-- FIXME: create an example nested pattern matching -->
This works for any single constructor variant. Here is a type named `tree` with a variable number of branches:
```ocaml
# type 'a tree = Node of 'a * 'a tree list;;
Expand All @@ -103,31 +167,12 @@ val tree_map : ('a -> 'b) -> 'a tree -> 'b tree = <fun>
- : int tree = Node (1, [Node (4, []); Node (9, []); Node (16, [])])
```

<!--FIXME: could something like this work? (From ChatGPT)
```
# type 'a option_value =
| Some of 'a
| None;;
type 'a option_value = Some of 'a | None
(* Define a function to create values using the variant *)
# let create_value x = Some x;;
val create_value : 'a -> 'a option_value = <fun>

(* Test the function by creating values *)
# let value1 = create_value 42;; (* Creating a value of type int *)
val value1 : int option_value = Some 42
(* Creating a value of type string *)
# let value2 = create_value "Hello";;
val value2 : string option_value = Some "Hello"
```
With or without the comments in between
-->

**Note**: Above, `'a` means “any type.” It is called a *type parameter* and is pronounced like the Greek letter α (“alpha”). This type parameter will be replaced by a type. The same goes for `'b` ("beta"), `'c` ("gamma"), etc. Any letter preceded by a `'` is a type parameter, also known as a [type variable](https://en.wikipedia.org/wiki/Type_variable).


<!--Records examples-->
Because records are implicitly single-constructor variants, this also applies to them:
```ocaml
# type name = { first : string; last: string };;
Expand All @@ -141,6 +186,7 @@ val first : string = "Robin"
val last : string = "Milner"
```

<!--Unit example-->
A special case of combined definition and pattern matching involves the `unit` type:
```ocaml
# let () = print_endline "ha ha";;
Expand All @@ -160,68 +206,14 @@ ha ha
- : unit = ()
```

<!-- `_` example-->
As seen in the last example, the catch-all pattern (`_`) can be used in definitions. The following example illustrates its use, which is distinct from the `()` pattern:
```ocaml
# let (_, y) = List.split [(1, 2); (3, 4); (5, 6); (7, 8)];;
val y : int list = [2; 4; 6; 8]
```
This construction creates two lists. The first is formed by the left element of each pair. The second is formed by the right element. Assuming we're only interested in the right elements, we give the name `y` to that list and discard the first by using `_`.

## Local Definitions

Local definitions are like global definitions, except the name is only bound inside an expression:
```ocaml
# let d = 2 * 3 in d * 7;;
- : int = 42
# d;;
Error: Unbound value d
```

Local definitions are introduced by the `let … = … in …` expression. The name bound before the `in` keyword is only bound in the expression after the `in` keyword. Here, the name `d` is bound to `6` inside the expression `d * 7`.

A couple of remarks:
- No global definition is introduced in this example, which is why we get an error.
- Computation of `2 * 3` will always take place before `d * 7`.

Local definitions can be chained (one after another) or nested (one inside another). Here is an example of chaining:
```ocaml
# let d = 2 * 3 in
let e = d * 7 in
d * e;;
- : int = 252
# d;;
Error: Unbound value d
# e;;
Error: Unbound value e
```

Here is how scoping works:
- `d` is bound to `6` inside `let e = d * 7 in d * e`
- `e` is bound to `42` inside `d * e`

Here is an example of nesting:
```ocaml
# let d =
let e = 2 * 3 in
e * 5 in
d * 7;;
- : int = 210
# d;;
Error: Unbound value d
# e;;
Error: Unbound value e
```
Here is how scoping works:
- `e` is bound to `6` inside `e * 5`
- `d` is bound to `30` inside `d * 7`

Arbitrary combinations of chaining or nesting are allowed.

In both examples, `d` and `e` are local definitions.

## Scopes and Environments

Without oversimplifying, an OCaml program is a sequence of expressions or global `let` definitions. These items are said to be at the _top-level_. An OCaml REPL, such as UTop, is called a "toplevel" because that's where typed definitions go.
Expand Down Expand Up @@ -356,7 +348,6 @@ This is just like a Unix shell pipe.
## Anonymous Functions

As citizens of the same level as other values, functions don't have to be bound to a name to exist (although some must, but this will be explained later). Take these examples:

```ocaml
# fun x -> x;;
- : 'a -> 'a = <fun>
Expand Down Expand Up @@ -413,8 +404,7 @@ Calling `sq` gets an error because it was only defined locally.

## Closures

<!--FIXME: Now that we've moved the term to after the example, what can we put here as an introduction?-->

This example illustrates a [*closure*](https://en.wikipedia.org/wiki/Closure_(computer_programming)) using [Same-Level Shadowing](#same-level-shadowing)
```ocaml
# let j = 2 * 3;;
val j : int = 6
Expand All @@ -432,8 +422,6 @@ val j : int = 7
- : int = 42
```

This example illustrates a [*closure*](https://en.wikipedia.org/wiki/Closure_(computer_programming)) using [Same-Level Shadowing](#same-level-shadowing)

Here is how this makes sense:
1. Constant `j` is defined, and its value is 6.
1. Function `k` is defined. It takes a single parameter `x` and returns its product by `j`.
Expand Down

0 comments on commit 486f5b3

Please sign in to comment.