Skip to content

Commit

Permalink
Run code samples through the compiler and fail the build if they don'…
Browse files Browse the repository at this point in the history
…t typecheck.

Requires a not yet realeased soupault hack as of soupault 1.8,
install from master if you want to build yourself.
  • Loading branch information
dmbaturin committed Feb 6, 2020
1 parent c61da56 commit 56744cb
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 31 deletions.
2 changes: 1 addition & 1 deletion book/algebraic-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ let is_whitespace c =
| '\t' -> true
| '\n' -> true
| '\r' -> true
_ -> false
| _ -> false
```

You can see that it's rather repetitive. To avoid repetition, OCaml allows conflating cases:
Expand Down
6 changes: 3 additions & 3 deletions book/conditional-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ right hand side of the operator must be of the same type. Trying to compare valu
will cause a compilation error. The type of those functions is an interesting question, for now we will only say that all equality and
comparison expressions evaluate to `bool`.

```ocaml
```
2 = 2 (* good *)
4 <> 3 (* good *)
Expand Down Expand Up @@ -61,8 +61,8 @@ The `<cond>` expression must be a `bool`.

None of these conditional expressions are valid:

```ocaml
if 1 then "one" else "two'
```invalid-ocaml
if 1 then "one" else "two"
if "" then 0 else 1
```
25 changes: 13 additions & 12 deletions book/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ to the value attached to `Some`.
For ease of use, the bind function is usually aliased to an infix operator, traditionally `>>=`.
Let's rewrite our example using the `Option.bind`:

```ocaml
```invalid-ocaml
let (>>=) = Option.bind
let handle_search_result o =
Expand All @@ -111,7 +111,7 @@ let () =
If we had multiple functions of type `'a -> 'a option`, we could write a much longer pipeline,
and still have to deal with unwrapping the option type explicitly only at the last step:

```ocaml
```invalid-ocaml
let y = x >>= f >>= g >>= h in
match y with
| None -> ...
Expand Down Expand Up @@ -164,7 +164,7 @@ exception Access_denied
exception Invalid_value of string
(* Parse error: line, column, error message *)
exception Parse_error (int * int * string)
exception Parse_error of (int * int * string)
```

### Raising and catching exceptions
Expand All @@ -182,14 +182,15 @@ let (//) x y =
else raise (Invalid_value "Attempted division by zero")
```

Now let's learn how to catch exceptions:
Now let's learn how to catch exceptions. Instead of inventing our own exception, we will use
a built-in exception named `Division_by_zero`:

```ocaml
let () =
try
let x = 4 // 0 in
let x = 4 / 0 in
Printf.printf "%d\n" x
with Invalid_value s -> print_endline s
with Division_by_zero -> print_endline "Cannot divide by zero"
```

Note that `try ... with` constructs are expressions rather than statements, and can be easily used
Expand All @@ -204,7 +205,7 @@ let () = Printf.printf "%d\n" x
Another implication of the fact that `try ... with` is an expression is that all expressions in the
`try` and `with` clauses must have the same type. This would cause a type error:

```ocaml
```invalid-ocaml
let x = try 4 / 0 with Division_by_zero -> print_endline "Division by zero"
```

Expand All @@ -213,27 +214,27 @@ You can catch multiple exceptions using a syntax similar to that of `match` expr
```ocaml
let () =
try
let x = 4 // 0 in
let x = 4 / 0 in
Printf.printf "%d\n" x
with
Invalid_value s -> print_endline s
Failure s -> print_endline s
| Division_by_zero -> print_endline "Division by zero"
```

So far our examples assumed that we know all exceptions our functions can possibly raise, or do not want
to catch any other exceptions, which is not always the case. Since there are no exception hierarchies,
we cannot catch a generic exception, but we can use the wildcard pattern to catch any possible exception.
The downside of course is that if an exception comes with an attached value, we cannot destructure
The downside of course is that if even if an exception comes with an attached value, we cannot destructure
it and extract the value since the type of that value is not known in advance.

```ocaml
let () =
try
let x = 4 // 0 in
let x = 4 / 0 in
Printf.printf "%d\n" x
with
Invalid_value s -> print_endline s
Division_by_zero -> print_endline "Cannot divide by zero"
| _ -> print_endline "Something went wrong"
```
Expand Down
8 changes: 5 additions & 3 deletions book/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ contains no other variables. When that function is applied to an argument, `x` w
with that argument in the `x * x` expression and that expression will be evaluated.
We can visualize that process like this:

```ocaml
```invalid-ocaml
let square = fun x -> x * x
square 3 =
(fun x -> x * x) 3 =
(fun 3 -> 3 * 3) =
Expand Down Expand Up @@ -188,7 +190,7 @@ this kind of errors rarely goes unnoticed and programs fail to compile.

Consider this program:

```ocaml
```invalid-ocaml
let add = fun x -> (fun y -> x + y)
let x = add 3 (* forgot second argument *)
Expand Down Expand Up @@ -248,7 +250,7 @@ In OCaml, every infix operator can also be used in a prefix form if enclosed in

```ocaml
let a = (+) 2 3
let b = (/.) 5 2
let b = (/.) 5. 2.
let c = (^) "hello " "world"
```

Expand Down
2 changes: 1 addition & 1 deletion book/lists-and-structural-recursion.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ The list type is defined as follows:

In OCaml we cannot create our own infix data constructors, but in imaginary syntax it could be written like this:

```ocaml
```invalid-ocaml
type 'a list = [] | 'a :: 'a list
```

Expand Down
2 changes: 1 addition & 1 deletion book/polymorphism.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ sides too.

Here is an example of an incorrect program:

```ocaml
```invalid-ocaml
let () = print_endline @@ 5
```

Expand Down
6 changes: 4 additions & 2 deletions book/records-and-references.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Let's write an example program that deals with 2d vectors defined by their compo
```ocaml
type vector = { x: float; y: float }
let vec = {x=1; y=1}
let vec = {x=1.0; y=1.0}
let length v =
(v.x *. v.x) +. (v.y *. v.y) |> sqrt
Expand Down Expand Up @@ -54,6 +54,8 @@ let person = {person with phone="212850A" }
It's fine to update multiple fields at once as well:

```ocaml
type contact = {name: string; phone: string}
let person = {name="Boris"; phone="2128506"}
let person = {person with name="Bob"; phone="212850B" }
Expand Down Expand Up @@ -92,7 +94,7 @@ There's a type `'a ref` that is really a record with a single mutable field name
```ocaml
type 'a ref = {mutable contents: 'a}
let ref value = {contents: value}
let ref value = {contents=value}
let (:=) reference value = reference.contents <- value
```
9 changes: 5 additions & 4 deletions book/values-and-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ easier to forget one. Since the wildcard pattern completely ignores the value, t

However, if you use the unit pattern, the program will fail to compile because `print_endline` function is not a value of type unit:

```ocaml
```invalid-ocaml
let () = print_endline
```

Expand Down Expand Up @@ -314,9 +314,10 @@ Consider this program:
(* Scope 0 *)
let hello = "hello "
let hello = hello ^ "world" in
(* Local scope 1 *)
print_endline hello
let () =
let hello = hello ^ "world" in
(* Local scope 1 *)
print_endline hello
(* Back to scope 0 *)
let () = print_endline hello
Expand Down
25 changes: 21 additions & 4 deletions soupault.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## Global settings

[settings]
strict = true
verbose = true

# Where generated files go
build_dir = "build/"

Expand Down Expand Up @@ -75,11 +78,25 @@
selector = "div#toc"
only_if_empty = true

[widgets.ocaml-compile]
widget = "preprocess_element"
selector = '.language-ocaml'
command = 'cat > /tmp/code_sample_$PPID.ml && ocamlc -stop-after typing /tmp/code_sample_$PPID.ml'
action = 'ignore_output'


# Runs the content of <* class="language-*"> elements through a syntax highlighter
[widgets.highlight]
# after = "graphviz-png"
[widgets.highlight-ocaml]
after = "ocaml-compile"
widget = "preprocess_element"
selector = '.language-ocaml'
command = 'highlight -O html -f --syntax=ocaml'
action = "replace_content"

[widgets.highlight-bad-ocaml]
after = "ocaml-compile"
widget = "preprocess_element"
selector = '*[class^="language-"]'
command = 'highlight -O html -f --syntax=$(echo $ATTR_CLASS | sed -e "s/language-//")'
selector = '.language-invalid-ocaml'
command = 'highlight -O html -f --syntax=ocaml'
action = "replace_content"

0 comments on commit 56744cb

Please sign in to comment.