aboutsummaryrefslogtreecommitdiff
path: root/documentation/book/the_lux_programming_language/chapter_4.md
blob: 201d0cc28d4e82d47e50ab5d7d500fde38bbc659 (plain)
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# Chapter 4: Functions and definitions

_Where you will learn how to build your own Lux code._

---

OK, so you've seen several explanations and details so far, but you haven't really seen how to make use of all of this information.

No worries. You're about to find out!

First, let's talk about how to make your own functions.

```clojure
(function (plus_two x) (++ (++ x)))
```

Here's the first example.

This humble function increases a `Nat` twice.

What is its type?

Well, I'm glad you asked.

```clojure
(: (-> Nat Nat)
   (function (plus_two x) (++ (++ x))))
```

That `->` thingie you see there is a macro for generating function types.
It works like this:

```clojure
(-> arg1 arg2 ,,, argN return)
```

The types of the arguments and the return type can be any type you want (even other function types, but more on that later).

How do we use our function? Just put it at the beginning for a form:

```clojure
((function (plus_two x) (++ (++ x))) 5)
... => 7
```

Cool, but... inconvenient.

It would be awful to have to use functions that way.

How do we use the `plus_two` function without having to inline its definition (like the `debug.log!` function we used previously)?

Well, we just need to define it!

```clojure
(def plus_two
  (: (-> Nat Nat)
     (function (_ x)
       (++ (++ x)))))
```

Or, alternatively:

```clojure
(def plus_two
  (-> Nat Nat)
  (function (_ x)
    (++ (++ x))))
```

Notice how the `def` macro can take the type of its value before the value itself, so we don't need to wrap it in the type-annotation `:` macro.

Now, we can use the square function more conveniently.

```clojure
(plus_two 7)
... => 9
```

Nice!

Also, I forgot to mention another form of the `def` macro which is even more convenient:

```clojure
(def (plus_two x)
  (-> Nat Nat)
  (++ (++ x)))
```

The `def` macro is very versatile, and it allows us to define constants and functions.

If you omit the type, the compiler will try to infer it for you, and you will get an error if there are any ambiguities.

You will also get an error if you add the types but there's something funny with your code and things don't match up.

Error messages keep improving on each release, but in general you'll be getting the **file, line and column** on which an error occurs, and, if it's a type-checking error, you'll usually get the type that was expected and the actual type of the offending expression... in multiple levels, as the type-checker analyses things in several steps.

That way, you can figure out what's going on by seeing the more localized error alongside the more general, larger-scope error.

---

Functions, of course, can take more than one argument, and you can even refer to a function within its own body (also known as recursion).

Check this one out:

```clojure
(def (factorial' acc n)
  (-> Nat Nat Nat)
  (if (n.= 0 n)
    acc
    (factorial' (n.* n acc) (-- n))))

(def (factorial n)
  (-> Nat Nat)
  (factorial' 1 n))
```

And if we just had the function expression itself, it would look like this:

```clojure
(function (factorial' acc n)
  (if (n.= 0 n)
    acc
    (factorial' (n.* n acc) (-- n))))
```

Here, we're defining the `factorial` function by counting down on the input and multiplying some accumulated value on each step.

We're using an intermediary function `factorial'` to have access to an accumulator for keeping the in-transit output value, and we're using an `if` expression (one of the many macros in the `library/lux` module) coupled with a recursive call to iterate until the input is 0 and we can just return the accumulated value.

As it is (hopefully) easy to see, the `if` expression takes a _test_ expression as its first argument, a _"then"_ expression as its second argument, and an _"else"_ expression as its third argument.

Both the `n.=` and the `n.*` functions operate on `Nat`s, and `--` is a function for decreasing `Nat`s; that is, to subtract 1 from the `Nat`.

You might be wondering what's up with that `n.` prefix.

The reason it exists is that Lux's arithmetic functions are not polymorphic on the numeric types, and so there are similar functions for each type.

If you import the module for `Nat` numbers, like so:

```clojure
(.require
 [library
  [lux
   [math
    [number
     ["n" nat]]]]])
```

You can access all definitions in the `library/lux/math/number/nat` module by just using the `n.` prefix.

Also, it might be good to explain that Lux functions can be partially applied.

This means that if a function takes N arguments, and you give it M arguments, where M < N, then instead of getting a compilation error, you'll just get a new function that takes the remaining arguments and then runs as expected.

That means, our factorial function could have been implemented like this:

```clojure
(def factorial
  (-> Nat Nat)
  (factorial' 1))
```

Or, to make it shorter:

```clojure
(def factorial (factorial' 1))
```

Nice, huh?

---

We've seen how to make our own definitions, which are the fundamental components in Lux programs.

We've also seen how to make functions, which is how you make your programs **do** things.

Next, we'll make things more interesting, with _branching_, _loops_ and _pattern-matching_!

See you in [the next chapter](chapter_5.md)!