1
0
Эх сурвалжийг харах

Major documentation update

David Peter 1 жил өмнө
parent
commit
9073f19d4f

+ 2 - 1
book/src/SUMMARY.md

@@ -33,9 +33,10 @@
   - [Conversions functions](./conversion-functions.md)
   - [Function definitions](./function-definitions.md)
   - [Conditionals](./conditionals.md)
+  - [Lists](./lists.md)
+  - [Structs](./structs.md)
   - [Date and time](./date-and-time.md)
   - [Printing, testing, debugging](./procedures.md)
-  - [Structs](./structs.md)
 - [Advanced](./advanced.md)
   - [Dimension definitions](./dimension-definitions.md)
   - [Unit definitions](./unit-definitions.md)

+ 1 - 1
book/src/conditionals.md

@@ -11,5 +11,5 @@ where `<cond>` is a condition that evaluates to a Boolean value, like
 For example, you can defined a simple step function using
 
 ```nbt
-fn step(x: Scalar) -> Scalar = if x < 0 then 0 else 1
+fn step(x) = if x < 0 then 0 else 1
 ```

+ 1 - 4
book/src/conversion-functions.md

@@ -30,9 +30,6 @@ now() -> tz("Asia/Kathmandu")
 # Convert a number to its hexadecimal representation
 2^31-1 -> hex
 
-# Convert a number to a custom base
-42 -> base(16)
-
 # Convert a code point number to a character
 0x2764 -> chr
 
@@ -44,5 +41,5 @@ now() -> tz("Asia/Kathmandu")
 "vier bis elf weiße Querbänder" -> lowercase
 ```
 
-Note that the `tz(…)` and `base(…)` calls above return *functions*, i.e. the right hand side of
+Note that the `tz(…)` call above *returns a function*, i.e. the right hand side of
 the conversion operator is still a function.

+ 2 - 2
book/src/example-factorial.md

@@ -1,13 +1,13 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # Factorial
-<a href="https://numbat.dev/?q=%23+Naive+factorial+implementation+to+showcase+recursive%0A%23+functions+and+conditionals.%0A%0Afn+factorial%28n%3A+Scalar%29+-%3E+Scalar+%3D%0A++if+n+%3C+1%0A++++then+1%0A++++else+n+%C3%97+factorial%28n+-+1%29%0A%0A%23+Compare+result+with+the+builtin+factorial+operator%0Aassert_eq%28factorial%2810%29%2C+10%21%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Naive+factorial+implementation+to+showcase+recursive%0A%23+functions+and+conditionals.%0A%0Afn+factorial%28n%29+%3D%0A++if+n+%3C+1%0A++++then+1%0A++++else+n+%C3%97+factorial%28n+-+1%29%0A%0A%23+Compare+result+with+the+builtin+factorial+operator%0Aassert_eq%28factorial%2810%29%2C+10%21%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Naive factorial implementation to showcase recursive
 # functions and conditionals.
 
-fn factorial(n: Scalar) -> Scalar =
+fn factorial(n) =
   if n < 1
     then 1
     else n × factorial(n - 1)

+ 2 - 2
book/src/example-population_growth.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # Population growth
-<a href="https://numbat.dev/?q=%23+Exponential+model+for+population+growth%0A%0Alet+initial_population+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+predict_population%28t%3A+Time%29+%3D%0A++++initial_population+%C3%97+e%5E%28growth_rate%C2%B7t%29+%7C%3E+round%0A%0Aprint%28%22Population+in++20+years%3A+%7Bpredict_population%2820+years%29%7D%22%29%0Aprint%28%22Population+in+100+years%3A+%7Bpredict_population%281+century%29%7D%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Exponential+model+for+population+growth%0A%0Alet+initial_population+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+predict_population%28t%29+%3D%0A++++initial_population+%C3%97+e%5E%28growth_rate%C2%B7t%29+%7C%3E+round%0A%0Aprint%28%22Population+in++20+years%3A+%7Bpredict_population%2820+years%29%7D%22%29%0Aprint%28%22Population+in+100+years%3A+%7Bpredict_population%281+century%29%7D%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Exponential model for population growth
@@ -9,7 +9,7 @@
 let initial_population = 50_000 people
 let growth_rate = 2% per year
 
-fn predict_population(t: Time) =
+fn predict_population(t) =
     initial_population × e^(growth_rate·t) |> round
 
 print("Population in  20 years: {predict_population(20 years)}")

+ 24 - 24
book/src/example-xkcd_2585.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # XKCD 2585
-<a href="https://numbat.dev/?q=%23+Rounding%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2585%2F%0A%0A17+mph%0A%0Aans+-%3E+meters%2Fsec++++%7C%3E+round%0Aans+-%3E+knots+++++++++%7C%3E+round%0Aans+-%3E+fathoms%2Fsec+++%7C%3E+round%0Aans+-%3E+furlongs%2Fmin++%7C%3E+round%0Aans+-%3E+fathoms%2Fsec+++%7C%3E+round%0Aans+-%3E+kph+++++++++++%7C%3E+round%0Aans+-%3E+knots+++++++++%7C%3E+round%0Aans+-%3E+kph+++++++++++%7C%3E+round%0Aans+-%3E+furlongs%2Fhour+%7C%3E+round%0Aans+-%3E+mph+++++++++++%7C%3E+round%0Aans+-%3E+m%2Fs+++++++++++%7C%3E+round%0Aans+-%3E+furlongs%2Fmin++%7C%3E+round%0Aans+-%3E+yards%2Fsec+++++%7C%3E+round%0Aans+-%3E+fathoms%2Fsec+++%7C%3E+round%0Aans+-%3E+m%2Fs+++++++++++%7C%3E+round%0Aans+-%3E+mph+++++++++++%7C%3E+round%0Aans+-%3E+furlongs%2Fmin++%7C%3E+round%0Aans+-%3E+knots+++++++++%7C%3E+round%0Aans+-%3E+yards%2Fsec+++++%7C%3E+round%0Aans+-%3E+fathoms%2Fsec+++%7C%3E+round%0Aans+-%3E+knots+++++++++%7C%3E+round%0Aans+-%3E+furlongs%2Fmin++%7C%3E+round%0Aans+-%3E+mph+++++++++++%7C%3E+round%0A%0Aprint%28%22I+can+ride+my+bike+at+%7Bans%7D.%22%29%0Aprint%28%22If+you+round.%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Rounding%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2585%2F%0A%0A17+mph%0A%0Aans+-%3E+meters%2Fsec++++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+kph+++++++++++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+kph+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fhour+-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0Aans+-%3E+m%2Fs+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+yards%2Fsec+++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+m%2Fs+++++++++++-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+yards%2Fsec+++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0A%0Aprint%28%22I+can+ride+my+bike+at+%7Bans%7D.%22%29%0Aprint%28%22If+you+round.%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Rounding
@@ -10,29 +10,29 @@
 
 17 mph
 
-ans -> meters/sec    |> round
-ans -> knots         |> round
-ans -> fathoms/sec   |> round
-ans -> furlongs/min  |> round
-ans -> fathoms/sec   |> round
-ans -> kph           |> round
-ans -> knots         |> round
-ans -> kph           |> round
-ans -> furlongs/hour |> round
-ans -> mph           |> round
-ans -> m/s           |> round
-ans -> furlongs/min  |> round
-ans -> yards/sec     |> round
-ans -> fathoms/sec   |> round
-ans -> m/s           |> round
-ans -> mph           |> round
-ans -> furlongs/min  |> round
-ans -> knots         |> round
-ans -> yards/sec     |> round
-ans -> fathoms/sec   |> round
-ans -> knots         |> round
-ans -> furlongs/min  |> round
-ans -> mph           |> round
+ans -> meters/sec    -> round
+ans -> knots         -> round
+ans -> fathoms/sec   -> round
+ans -> furlongs/min  -> round
+ans -> fathoms/sec   -> round
+ans -> kph           -> round
+ans -> knots         -> round
+ans -> kph           -> round
+ans -> furlongs/hour -> round
+ans -> mph           -> round
+ans -> m/s           -> round
+ans -> furlongs/min  -> round
+ans -> yards/sec     -> round
+ans -> fathoms/sec   -> round
+ans -> m/s           -> round
+ans -> mph           -> round
+ans -> furlongs/min  -> round
+ans -> knots         -> round
+ans -> yards/sec     -> round
+ans -> fathoms/sec   -> round
+ans -> knots         -> round
+ans -> furlongs/min  -> round
+ans -> mph           -> round
 
 print("I can ride my bike at {ans}.")
 print("If you round.")

+ 1 - 3
book/src/example-xkcd_2812.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # XKCD 2812
-<a href="https://numbat.dev/?q=%23+Solar+panel+placement%0A%23%0A%23+Solar+energy+tip%3A+To+maximize+sun+exposure%2C+always%0A%23+orient+your+panels+downward+and+install+them+on+the%0A%23+surface+of+the+sun.%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2812%2F%0A%23%0A%23+%5B1%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSolar_luminosity%0A%23+%5B2%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSun%0A%0Aunit+%24%3A+Money%0A%0Alet+net_metering_rate+%3D+%24+0.20+%2F+kWh%0Alet+panel_area+%3D+1+m%C2%B2%0Alet+panel_efficiency+%3D+20+%25%0A%0Afn+savings%28i%3A+Irradiance%29+-%3E+Money+%2F+Time+%3D%0A++++net_metering_rate+%C3%97+i+%C3%97+panel_area+%C3%97+panel_efficiency+-%3E+%24%2Fyear%0A%0Aprint%28%22Option+A%3A+On+the+roof%2C+south+facing%22%29%0A%0Alet+savings_a+%3D+savings%284+kWh%2Fm%C2%B2%2Fday%29%0A%0Aprint%28savings_a+%7C%3E+round%29%0A%0Aprint%28%29%0Aprint%28%22Option+B%3A+On+the+sun%2C+downward+facing%22%29%0A%0Adimension+Luminosity+%3D+Power%0A%0Alet+sun_luminosity%3A+Luminosity+%3D+3.828e26+W++%23+%5B1%5D%0Alet+sun_area%3A+Area+%3D+6.09e12+km%5E2++++++++++++%23+%5B2%5D%0A%0Alet+savings_b+%3D+savings%28sun_luminosity+%2F+sun_area%29%0A%0Aprint%28savings_b+%7C%3E+round%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Solar+panel+placement%0A%23%0A%23+Solar+energy+tip%3A+To+maximize+sun+exposure%2C+always%0A%23+orient+your+panels+downward+and+install+them+on+the%0A%23+surface+of+the+sun.%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2812%2F%0A%23%0A%23+%5B1%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSolar_luminosity%0A%23+%5B2%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSun%0A%0Alet+net_metering_rate+%3D+%24+0.20+%2F+kWh%0Alet+panel_area+%3D+1+m%C2%B2%0Alet+panel_efficiency+%3D+20+%25%0A%0Afn+savings%28i%3A+Irradiance%29+-%3E+Money+%2F+Time+%3D%0A++++net_metering_rate+%C3%97+i+%C3%97+panel_area+%C3%97+panel_efficiency+-%3E+%24%2Fyear%0A%0Aprint%28%22Option+A%3A+On+the+roof%2C+south+facing%22%29%0A%0Alet+savings_a+%3D+savings%284+kWh%2Fm%C2%B2%2Fday%29%0A%0Aprint%28savings_a+%7C%3E+round%29%0A%0Aprint%28%29%0Aprint%28%22Option+B%3A+On+the+sun%2C+downward+facing%22%29%0A%0Adimension+Luminosity+%3D+Power%0A%0Alet+sun_luminosity%3A+Luminosity+%3D+3.828e26+W++%23+%5B1%5D%0Alet+sun_area%3A+Area+%3D+6.09e12+km%5E2++++++++++++%23+%5B2%5D%0A%0Alet+savings_b+%3D+savings%28sun_luminosity+%2F+sun_area%29%0A%0Aprint%28savings_b+%7C%3E+round%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Solar panel placement
@@ -15,8 +15,6 @@
 # [1] https://en.wikipedia.org/wiki/Solar_luminosity
 # [2] https://en.wikipedia.org/wiki/Sun
 
-unit $: Money
-
 let net_metering_rate = $ 0.20 / kWh
 let panel_area = 1 m²
 let panel_efficiency = 20 %

+ 77 - 25
book/src/function-definitions.md

@@ -9,48 +9,90 @@ fn max_distance(v: Velocity, θ: Angle) -> Length = v² · sin(2 θ) / g0
 ```
 
 This exemplary function computes the maximum distance of a projectile under the
-influence of Earths gravity. It takes two parameters (The initial velocity `v` and
+influence of Earths gravity. It takes two parameters (the initial velocity `v` and
 the launch angle `θ`), which are both annotated with their corresponding physical
 dimension (their type). The function returns a distance, and so the return type
 is specified as `Length`.
 
 ## Type inference
 
-The return type annotation may be omitted, but it is often desirable to add it
-for better readability of the code and in order to catch potential errors.
-
-The parameter types can also (sometimes) be omitted, in which case Numbat tries
-to infer their type. However, this often leads to overly generic function
-signatures. For example, consider the following function to compute the kinetic
-energy of a massive object in motion:
+Numbat has a powerful type inference system, which is able to infer missing types
+when they are not explicitly specified. For example, consider the following function
+definition for the breaking distance of a car, given its velocity `v`:
+```nbt
+fn breaking_distance(v) = v t_reaction + v² / 2 µ g0
+  where t_reaction = 1 s # driver reaction time
+    and µ = 0.7          # coefficient of friction
+```
+If you enter this function into the Numbat REPL, you will see that all types are filled
+in automatically:
+```nbt
+fn breaking_distance(v: Velocity) -> Length = v × t_reaction + (v² / (2 µ × g0))
+  where t_reaction: Time = 1 second
+    and µ: Scalar = 0.7
+```
+In particular, note that the type of the function argument `v` is correctly inferred as
+`Velocity`, and the return type is `Length`.
+
+> **Note**: This is possible because the types of `t_reaction`, `µ`, and `g0` (gravitational acceleration)
+> are known. The `+` operator imposes a *constraint* on the types: two quantities can
+> only be added if their physical dimension is the same. The type inference algorithm
+> records constraints like this, and then tries to find a solution that satisfies all
+> all of them. In this case, only a single equation needs to be solved:
+> ```
+> type(v) × type(t_reaction) = type(v)² / (type(µ) × type(g0)      )
+> type(v) × Time             = type(v)² / (      1 × Length / Time²)
+> ```
+> which has the solution `type(v) = Length / Time = Velocity`. Note that this also
+> works if there are multiple constraints on the types. In fact, type inference is
+> always decidable.
+
+The fact that it is *possible* to omit type annotations does not mean that it is always
+a good idea to do so.
+Type annotations can help to make the code more readable and can also help to catch
+errors earlier.
+
+In some cases, type inference will also lead to function types that are overly generic.
+For example, consider the following function to compute the kinetic energy of a massive
+object in motion:
 
 ```nbt
 fn kinetic_energy(mass, speed) = 1/2 * mass * speed^2
 ```
 
-Without any type annotations, this function has an overly generic type where
-`mass` and `speed` can have arbitrary dimensions (and the return type is
-`type(mass) * type(speed)^2`). So for this case, it is probably better to add
-parameter and return types.
+In the absence of any type annotations, this function has an overly generic type where
+`mass` and `speed` can have arbitrary dimensions (but the return type is constrained
+accordingly):
+```nbt
+fn kinetic_energy<A: Dim, B: Dim>(mass: A, speed: B) -> A × B² = …
+```
+In this example, it would be better to specify the types of `mass` and `speed`
+explicitly (`Mass`, `Velocity`). The return type can then be inferred (`Energy`).
+It is still valuable to specify it explicitly, in order to ensure there are no
+mistakes in the function implementation.
 
 ## Generic functions
 
-Sometimes however, it *is* useful to write generic functions. For example, consider
+Sometimes it is useful to write generic functions. For example, consider
 `max(a, b)` — a function that returns the larger of the two arguments. We might
 want to use that function with *dimensionful* arguments such as `max(1 m, 1 yd)`.
 To define such a generic function, you can introduce *type parameters* in angle
 brackets:
 
 ```nbt
-fn max<T>(a: T, b: T) -> T = if a > b then a else b
+fn max<D: Dim>(a: D, b: D) -> D =
+  if a > b then a else b
 ```
 
 This function signature tells us that `max` takes two arguments of *arbitrary*
-type `T` (but they need to match!), and returns a quantity of the same type `T`.
+dimension type `D` (but they need to match!), and returns a quantity of the same
+type `D`. The `D: Dim` syntax is a *type constraint* (or bound) that ensures that
+`D` is a dimension type (`Scalar`, `Length`, `Velocity`, etc), and not something
+like `Bool` or `DateTime`.
 
-Note that you can perform the usual operations with type parameters, such as
-multiplying/dividing them with other types, or raising to rational powers. For
-example, consider this cube-root function
+Note that you can perform the usual operations with (dimension) type parameters,
+such as multiplying / dividing them with other types, or raising to rational powers.
+For example, consider this cube-root function
 
 ```nbt
 fn cube_root<T>(x: T^3) -> T = x^(1/3)
@@ -62,17 +104,27 @@ argument (`cube_root(1 liter) == 10 cm`).
 Note: `cube_root` can also be defined as `fn cube_root<T>(x: T) -> T^(1/3)`,
 which is equivalent to the definition above.
 
-## Recursive functions
+Functions can also be generic over *all* types, not just dimension types. In this case,
+no type constraints are needed. For example:
+```nbt
+fn second_element<A>(xs: List<A>) -> A =
+  head(tail(xs))
+
+second_element([10 cm, 2 m, 3 inch]) # returns 2 m
+second_element(["a", "b", "c"])      # returns "b"
+```
+
+Note that the type annotations for all examples in this section are optional and
+can also be inferred.
 
-It is also possible to define recursive functions. In order to do so, you
-currently need to specify the return type — as the type signature can not
-(yet) be inferred otherwise.
+## Recursive functions
 
-For example, a naive recursive implementation to compute Fibonacci numbers
-in Numbat looks like this:
+It is also possible to define recursive functions. For example, a naive
+recursive implementation to compute Fibonacci numbers in Numbat looks like
+this:
 
 ```nbt
-fn fib(n: Scalar) -> Scalar =
+fn fib(n) =
   if n ≤ 2
     then 1
     else fib(n - 2) + fib(n - 1)

+ 2 - 2
book/src/introduction.md

@@ -52,5 +52,5 @@ The real strength of Numbat, however, is to perform calculations with physical u
     = 1.87855 eV    [Energy]
 ```
 
-Read the [tutorial](./tutorial.md) to learn more about the language. Or jump directly
-to the [syntax reference](./example-numbat_syntax.md).
+Read the [tutorial](./tutorial.md) to learn more about the language or look at some [example programs](./examples.md).
+You can also jump directly to the [syntax reference](./example-numbat_syntax.md).

+ 7 - 0
book/src/list-functions-other.md

@@ -33,6 +33,13 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.
 fn is_infinite<T: Dim>(n: T) -> Bool
 ```
 
+### `is_finite`
+Returns true if the input is neither infinite nor `NaN`.
+
+```nbt
+fn is_finite<T: Dim>(n: T) -> Bool
+```
+
 ## Quantities
 
 Defined in: `core::quantities`

+ 7 - 7
book/src/list-functions-strings.md

@@ -79,6 +79,13 @@ Repeat the input string `n` times.
 fn str_repeat(a: String, n: Scalar) -> String
 ```
 
+### `base`
+Convert a number to the given base. Example: `42 |> base(16)`.
+
+```nbt
+fn base(b: Scalar, x: Scalar) -> String
+```
+
 ### `bin`
 Get a binary representation of a number. Example: `42 -> bin`.
 
@@ -107,10 +114,3 @@ Get a hexadecimal representation of a number. Example: `2^31-1 -> hex`.
 fn hex(x: Scalar) -> String
 ```
 
-### `base`
-Convert a number to the given base. Example: `42 -> base(16)`.
-
-```nbt
-fn base(b: Scalar) -> Fn[(Scalar) -> String]
-```
-

+ 38 - 0
book/src/lists.md

@@ -0,0 +1,38 @@
+# Lists
+
+Numbat has a built-in data type for lists. The elements can be of any type, including other lists.
+Lists can be created using the `[…]` syntax. For example:
+
+```nbt
+[30 cm, 110 cm, 2 m]
+["a", "b", "c"]
+[[1, 2], [3, 4]]
+```
+
+The type of a list is written as `List<T>`, where `T` is the type of the elements. The types of the lists
+above are `List<Length>`, `List<String>`, and `List<List<Scalar>>`, respectively.
+
+The standard library provides a [number of functions](./list-functions-lists.md) to work with lists. Some
+useful things to do with lists are:
+```nbt
+# Get the length of a list
+len([1, 2, 3])  # returns 3
+
+# Sum all elements of a list:
+sum([30 cm, 130 cm, 2 m])  # returns 360 cm
+
+# Get the average of a list:
+mean([30 cm, 130 cm, 2 m])  # returns 120 cm
+
+# Filter a list:
+filter(is_finite, [20 cm, inf, 1 m])  # returns [20 cm, 1 m]
+
+# Map a function over a list:
+map(sqr, [10 cm, 2 m])  # returns [100 cm², 4 m²]
+
+# Generate a range of numbers:
+range(1, 5)  # returns [1, 2, 3, 4, 5]
+
+# Generate a list of evenly spaced quantities:
+linspace(0 m, 1 m, 5)  # returns [0 m, 0.25 m, 0.5 m, 0.75 m, 1 m]
+```

+ 9 - 4
book/src/number-notation.md

@@ -29,12 +29,17 @@ so you can write expressions like:
 
 Examples:
 ```nbt
+0xffee -> bin
+42 -> oct
+2^16 - 1 -> hex
+
+# using 'to':
 0xffee to bin
-42 to oct
-2^16 - 1 to hex
 ```
 
-You can also use `base(b)` to convert a number to base `b`:
+You can also use `base(b, n)` to convert a number `n` to base `b`. Using the reverse function application operator `|>` you can write
+this in a similar style to the previous examples:
 ```nbt
-0xffee to base(2)
+273 |> base(3)
+144 |> base(12)
 ```

+ 1 - 1
book/src/operations.md

@@ -20,7 +20,7 @@ Numbat operators and other language constructs, ordered by precedence form *high
 | logical 'or'              | <code>x &#124;&#124; y</code>        |
 | unit conversion           | `x -> y`, `x → y`, `x ➞ y`, `x to y` |
 | conditionals              | `if x then y else z`                 |
-| reverse function call     | `x |> f`                             |
+| reverse function call     | `x \|> f`                            |
 
 Note that *implicit* multiplication has a higher precedence than division, i.e. `50 cm / 2 m` will be parsed as `50 cm / (2 m)`.
 

+ 1 - 1
book/src/procedures.md

@@ -61,7 +61,7 @@ There is also a plain `assert` procedure that can test any boolean condition. Fo
 
 ```nbt
 assert(1 yard < 1 meter)
-assert(π != 3)
+assert(str_contains("foobar", "bar"))
 ```
 
 A runtime error is thrown if an assertion fails. Otherwise, nothing happens.

+ 17 - 12
book/src/type-system.md

@@ -55,7 +55,7 @@ dimension Power = Energy / Time
 
 ## Type inference and type annotations
 
-The type checker can infer the types of (most) expressions without explicitly declaring them. For example,
+The type checker can infer the types of all expressions without explicitly declaring them. For example,
 the following definition does not mention any types:
 ``` numbat
 let E_pot = 80 kg × 9.8 m/s² × 5 m
@@ -71,22 +71,28 @@ Function definitions also allow for type annotations, both for the parameters as
 let p0: Pressure = 101325 Pa
 let t0: Temperature = 288.15 K
 
-let gradient = 0.65 K / 100 m
+let lapse_rate = 0.65 K / 100 m
 
-fn air_pressure(height: Length) -> Pressure = p0 · (1 - gradient · height / t0)^5.255
+fn air_pressure(height: Length) -> Pressure =
+  p0 · (1 - lapse_rate · height / t0)^5.255
 ```
+See [this chapter](./function-definitions.md) for more details on the type inference algorithm.
 
 
 ## Generic types
 
-Numbat's type system also supports generic types (type polymorphism).
+Numbat's type system also supports generic types (parametric polymorphism).
 These can be used for functions that work regardless of the physical dimension of the argument(s).
 For example, the type signature of the absolute value function is given by
 ``` numbat
-fn abs<D>(x: D) -> D
+fn abs<D: Dim>(x: D) -> D
 ```
 where the angle brackets after the function name introduce new type parameters (`D`).
-This can be read as: `abs` takes an arbitrary physical quantity of dimension `D` and returns a quantity of the *same* physical dimension `D`.
+This can be read as: `abs` takes an arbitrary physical quantity of dimension type `D`
+and returns a quantity of the *same* physical dimension `D`.
+
+The `Dim` constraint makes sure that this can not be used with non-dimensional types like `Bool`
+or `String`.
 
 As a more interesting example, we can look at the `sqrt` function. Its type signature can be written as
 ``` numbat
@@ -133,9 +139,8 @@ requires that:
     * `expr1` is also of type `Scalar`
     * `expr2` can be *evaluated at compile time* and yields a rational number.
 
-#### Remark
-
-We would probably need to enter the world of *dependent types* if we wanted to fully
-support exponentiation expressions without the limitations above. For example, consider
-the function `f(x, n) = x^n`. The return type of that function *depends on the value*
-of the parameter `n`.
+> **Remark**:
+> We would probably need to enter the world of *dependent types* if we wanted to fully
+> support exponentiation expressions without the limitations above. For example, consider
+> the function `f(x, n) = x^n`. The return type of that function *depends on the value*
+> of the parameter `n`.

+ 1 - 1
book/src/web-usage.md

@@ -56,6 +56,6 @@ In interactive command-line mode, you can use the following key bindings. Most i
 To share the result of a calculation with someone else, you can just copy the URL from
 your browers address bar. As you enter new lines in the terminal, your input will be
 appended to the URL to build up something like
-[`https://numbat.dev/?q=let+P0+%3D+50_000+people%0A…`](https://numbat.dev/?q=let+P0+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+population(t%3A+Time)+%3D%0A++++P0+×+e^(growth_rate·t)+%2F%2F+round%0A%0Aprint("P(20+years)+%3D+{population(20+years)}"))
+[`https://numbat.dev/?q=let+P0+%3D+50_000+people%0A…`](https://numbat.dev/?q=let+P0+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+population(t%3A+Time)+%3D%0A++++P0+×+e^(growth_rate·t)+%7C%3E+round%0A%0Aprint("P(20+years)+%3D+{population(20+years)}"))
 that you can just copy and share. To reset the state and clear the URL, use the `reset`
 command (see above).

+ 1 - 1
examples/factorial.nbt

@@ -1,7 +1,7 @@
 # Naive factorial implementation to showcase recursive
 # functions and conditionals.
 
-fn factorial(n: Scalar) -> Scalar =
+fn factorial(n) =
   if n < 1
     then 1
     else n × factorial(n - 1)

+ 1 - 1
examples/population_growth.nbt

@@ -3,7 +3,7 @@
 let initial_population = 50_000 people
 let growth_rate = 2% per year
 
-fn predict_population(t: Time) =
+fn predict_population(t) =
     initial_population × e^(growth_rate·t) |> round
 
 print("Population in  20 years: {predict_population(20 years)}")

+ 35 - 20
examples/tests/base_conversion.nbt

@@ -1,22 +1,37 @@
-assert_eq((0b0 -> bin), "0b0")
-assert_eq((0b1 -> bin), "0b1")
-assert_eq((0b10 -> bin), "0b10")
-assert_eq((0b11 -> bin), "0b11")
-assert_eq((0b10101010101010101010101010101010 -> bin), "0b10101010101010101010101010101010")
-assert_eq((-0b11110000 -> bin), "-0b11110000")
+assert_eq(0b0 -> bin, "0b0")
+assert_eq(0b1 -> bin, "0b1")
+assert_eq(0b10 -> bin, "0b10")
+assert_eq(0b11 -> bin, "0b11")
+assert_eq(0b10101010101010101010101010101010 -> bin, "0b10101010101010101010101010101010")
+assert_eq(-0b11110000 -> bin, "-0b11110000")
 
-assert_eq((0o0 -> oct), "0o0")
-assert_eq((0o1 -> oct), "0o1")
-assert_eq((0o7 -> oct), "0o7")
-assert_eq((0o10 -> oct), "0o10")
-assert_eq((0o77 -> oct), "0o77")
-assert_eq((0o12345670 -> oct), "0o12345670")
-assert_eq((-0o12345670 -> oct), "-0o12345670")
+assert_eq(0o0 -> oct, "0o0")
+assert_eq(0o1 -> oct, "0o1")
+assert_eq(0o7 -> oct, "0o7")
+assert_eq(0o10 -> oct, "0o10")
+assert_eq(0o77 -> oct, "0o77")
+assert_eq(0o12345670 -> oct, "0o12345670")
+assert_eq(-0o12345670 -> oct, "-0o12345670")
 
-assert_eq((0x0 -> hex), "0x0")
-assert_eq((0x1 -> hex), "0x1")
-assert_eq((0x9 -> hex), "0x9")
-assert_eq((0xa -> hex), "0xa")
-assert_eq((0xf -> hex), "0xf")
-assert_eq((0xabc1234567890 -> hex), "0xabc1234567890")
-assert_eq((-0xc0ffee -> hex), "-0xc0ffee")
+assert_eq(0x0 -> hex, "0x0")
+assert_eq(0x1 -> hex, "0x1")
+assert_eq(0x9 -> hex, "0x9")
+assert_eq(0xa -> hex, "0xa")
+assert_eq(0xf -> hex, "0xf")
+assert_eq(0xabc1234567890 -> hex, "0xabc1234567890")
+assert_eq(-0xc0ffee -> hex, "-0xc0ffee")
+
+assert_eq(1 |> base(3), "1")
+assert_eq(2 |> base(3), "2")
+assert_eq(3 |> base(3), "10")
+assert_eq(4 |> base(3), "11")
+assert_eq(5 |> base(3), "12")
+assert_eq(6 |> base(3), "20")
+assert_eq(42 |> base(3), "1120")
+
+assert_eq(1 |> base(12), "1")
+assert_eq(10 |> base(12), "a")
+assert_eq(11 |> base(12), "b")
+assert_eq(12 |> base(12), "10")
+assert_eq(13 |> base(12), "11")
+assert_eq(42 |> base(12), "36")

+ 23 - 23
examples/xkcd_2585.nbt

@@ -4,29 +4,29 @@
 
 17 mph
 
-ans -> meters/sec    |> round
-ans -> knots         |> round
-ans -> fathoms/sec   |> round
-ans -> furlongs/min  |> round
-ans -> fathoms/sec   |> round
-ans -> kph           |> round
-ans -> knots         |> round
-ans -> kph           |> round
-ans -> furlongs/hour |> round
-ans -> mph           |> round
-ans -> m/s           |> round
-ans -> furlongs/min  |> round
-ans -> yards/sec     |> round
-ans -> fathoms/sec   |> round
-ans -> m/s           |> round
-ans -> mph           |> round
-ans -> furlongs/min  |> round
-ans -> knots         |> round
-ans -> yards/sec     |> round
-ans -> fathoms/sec   |> round
-ans -> knots         |> round
-ans -> furlongs/min  |> round
-ans -> mph           |> round
+ans -> meters/sec    -> round
+ans -> knots         -> round
+ans -> fathoms/sec   -> round
+ans -> furlongs/min  -> round
+ans -> fathoms/sec   -> round
+ans -> kph           -> round
+ans -> knots         -> round
+ans -> kph           -> round
+ans -> furlongs/hour -> round
+ans -> mph           -> round
+ans -> m/s           -> round
+ans -> furlongs/min  -> round
+ans -> yards/sec     -> round
+ans -> fathoms/sec   -> round
+ans -> m/s           -> round
+ans -> mph           -> round
+ans -> furlongs/min  -> round
+ans -> knots         -> round
+ans -> yards/sec     -> round
+ans -> fathoms/sec   -> round
+ans -> knots         -> round
+ans -> furlongs/min  -> round
+ans -> mph           -> round
 
 print("I can ride my bike at {ans}.")
 print("If you round.")

+ 0 - 2
examples/xkcd_2812.nbt

@@ -9,8 +9,6 @@
 # [1] https://en.wikipedia.org/wiki/Solar_luminosity
 # [2] https://en.wikipedia.org/wiki/Sun
 
-unit $: Money
-
 let net_metering_rate = $ 0.20 / kWh
 let panel_area = 1 m²
 let panel_efficiency = 20 %

+ 3 - 0
numbat/modules/core/numbers.nbt

@@ -5,3 +5,6 @@ fn is_nan<T: Dim>(n: T) -> Bool
 @description("Returns true if the input is positive infinity or negative infinity.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite")
 fn is_infinite<T: Dim>(n: T) -> Bool
+
+@description("Returns true if the input is neither infinite nor `NaN`.")
+fn is_finite<T: Dim>(n: T) -> Bool = !is_nan(n) && !is_infinite(n)

+ 13 - 26
numbat/modules/core/strings.nbt

@@ -66,45 +66,32 @@ fn _hex_digit(x: Scalar) -> String =
   where
     x_16 = mod(x, 16)
 
-fn _digit_in_base(x: Scalar, base: Scalar) -> String =
+fn _digit_in_base(base: Scalar, x: Scalar) -> String =
   if base < 2 || base > 16
     then error("base must be between 2 and 16")
     else if x_16 < 10 then chr(48 + x_16) else chr(97 + x_16 - 10)
   where
     x_16 = mod(x, 16)
 
-fn _number_in_base(x: Scalar, b: Scalar) -> String =
+# TODO: once we have anonymous functions / closures, we can implement base in a way
+# that it returns a partially-applied version of '_number_in_base'. This would allow
+# arbitrary 'x -> base(b)' conversions.
+@description("Convert a number to the given base. Example: `42 |> base(16)`")
+fn base(b: Scalar, x: Scalar) -> String =
   if x < 0
-    then "-{_number_in_base(-x, b)}"
+    then "-{base(b, -x)}"
     else if x < b
-      then _digit_in_base(x, b)
-      else str_append(_number_in_base(floor(x / b), b), _digit_in_base(mod(x, b), b))
+      then _digit_in_base(b, x)
+      else str_append(base(b, floor(x / b)), _digit_in_base(b, mod(x, b)))
 
 @description("Get a binary representation of a number. Example: `42 -> bin`")
-fn bin(x: Scalar) -> String = if x < 0 then "-{bin(-x)}" else "0b{_number_in_base(x, 2)}"
+fn bin(x: Scalar) -> String = if x < 0 then "-{bin(-x)}" else "0b{base(2, x)}"
 
 @description("Get an octal representation of a number. Example: `42 -> oct`")
-fn oct(x: Scalar) -> String = if x < 0 then "-{oct(-x)}" else "0o{_number_in_base(x, 8)}"
+fn oct(x: Scalar) -> String = if x < 0 then "-{oct(-x)}" else "0o{base(8, x)}"
 
 @description("Get a decimal representation of a number.")
-fn dec(x: Scalar) -> String = _number_in_base(x, 10)
+fn dec(x: Scalar) -> String = base(10, x)
 
 @description("Get a hexadecimal representation of a number. Example: `2^31-1 -> hex`")
-fn hex(x: Scalar) -> String = if x < 0 then "-{hex(-x)}" else "0x{_number_in_base(x, 16)}"
-
-# TODO: once we have anonymous functions / closures, we can implement base in a way
-# that it returns a partially-applied version of '_number_in_base'. This would allow
-# arbitrary 'x -> base(b)' conversions.
-fn _not_implemented(unused: Scalar) -> String = error("bases other than 2, 8, 10, and 16 are currently not implemented")
-
-@description("Convert a number to the given base. Example: `42 -> base(16)`")
-fn base(b: Scalar) -> Fn[(Scalar) -> String] =
-  if b == 2
-    then bin
-    else if b == 8
-      then oct
-      else if b == 10
-        then dec
-        else if b == 16
-          then hex
-          else _not_implemented
+fn hex(x: Scalar) -> String = if x < 0 then "-{hex(-x)}" else "0x{base(16, x)}"

+ 1 - 0
numbat/tests/common.rs

@@ -21,6 +21,7 @@ pub fn get_test_context() -> Context {
         let mut context = get_test_context_without_prelude();
 
         let _ = context.interpret("use prelude", CodeSource::Internal)?;
+        let _ = context.interpret("use units::currencies", CodeSource::Internal)?;
         Ok(context)
     });