You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Closures and unit tests.
I think there should only be one syntax for declaring a function,
whether it's named or anonymous. I like how Javascript lets you do this
with `const myFunc = (x) => {x+1}`. We should take inspiration from
that.
New function syntax is
```kcl
(params -> return type) => expr
```
If you want to bind this function to a name, just do so!
```kcl
myFunctionName = (params -> return type) => expr
```
* We don't need result because there's no fallible operations like network requests
* Review from Frank
* Fix Frank's comment
From #1 (comment)
* Feedback from Jess re GLTF
Copy file name to clipboardExpand all lines: fantasy-docs.md
+92-26Lines changed: 92 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -141,11 +141,9 @@ A measurement type (e.g. `Force` or `Distance`) may have a lot of different cons
141
141
c. `Distance.Metre(1)`
142
142
d. `Distance.Yard(0.9144)`
143
143
2. A combination of different units, using a standard mathematical definition, e.g.
144
-
a. `Force.mdt(Mass::Kilogram(1), Distance::Metre(1), Time::Second(1))` (force = mass * distance * time)
145
-
b. `Area.rectangle(Distance::Metre(1), Distance::Metre(2))` (area = distance_x * distance_y)
146
-
c. `Area.rectangle(Distance::Metre(1), Distance::Yard(1.8288))` (same equation as above, but mixing imperial and metric)
147
-
148
-
2. As a combination of named units, e.g. `x = Force<Kilogram, Metre, Second>(675.559)`
144
+
a. `Force.mdt(Mass.Kilogram(1), Distance.Metre(1), Time.Second(1))` (force = mass * distance * time)
145
+
b. `Area.rectangle(Distance.Metre(1), Distance.Metre(2))` (area = distance_x * distance_y)
146
+
c. `Area.rectangle(Distance.Metre(1), Distance.Yard(1.8288))` (same equation as above, but mixing imperial and metric)
149
147
150
148
Both these expressions have the same _type_ (`Force`) but use different _constructors_ to give the specific value. For more about syntax, see the Syntax section below.
151
149
@@ -231,7 +229,7 @@ Enums can have fields associated with them:
231
229
enum ExtrudeTop = Open | Closed(Material)
232
230
```
233
231
234
-
has two possible variants. The first variant is `Open` and this variant only has one possible value: the `Open` itself. The second variant,`Closed`, has a field `Material`, so if you create a value of type `ExtrudeTop::Material` it must also have a `Material`.
232
+
has two possible variants. The first variant is `Open` and this variant only has one possible value: the `Open` itself. The second variant,`Closed`, has a field `Material`, so if you create a value of type `ExtrudeTop.Material` it must also have a `Material`.
235
233
236
234
The standard library uses enums, and users can create their own. There are no nulls or undefined in KCL, instead, we use the Option enum:
This enum has two variants. Either it is `None` or it's `Some`, and if it's `Some` then it also has a value of type `a` (see the "Type variables" section above).
243
241
244
-
There is also `enum Result t e = Ok(t) | Err(e)`, which is a useful way to express operations that might succeed or fail with an error value. Option and Result work just like they do in other languages (see Java, Swift, Rust, Haskell, Elm).
245
-
246
242
## Syntax
247
243
248
244
### Functions
@@ -251,21 +247,21 @@ A KCL program is made up of _functions_. A function has a name, parameters, and
251
247
252
248
```kcl
253
249
/// A can for our line of *awesome* new [baked beans](https://example.com/beans).
1. Docstring: a comment which describes the function below it. Your KCL editor probably supports showing this comment when you mouse over the function, or over a visualization of the 3D shape it outputs. Your editor/viewer also probably supports Markdown.
261
-
2. This is the function signature. It lets readers (and the Puffin compiler) know this is a function called "can_of_beans". The function takes two parameters, called "radius" and "height". Both parameters have type Distance. It returns a Solid3d. The "=" sign marks the end of the function signature and the start of the expression/function body.
262
-
3.First line of the function body. This calls the function "circle" with the parameter "radius".
257
+
2. This is where the function starts. First is the function name, "can_of_beans". The name is followed by an `=`, then the function signature. The signature describes the function's parameters and return types. This function takes two parameters, called "radius" and "height". Both parameters have type Distance. It returns a Solid3d. The `=>`marks the end of the function signature, and the start of the function body.
258
+
3.The function body is an _expression_. The first line of the expression is calling the function "circle" with the parameter "radius".
263
259
4. The |> operator composes two functions. If you see `f |> g` it means "calculate `f` then apply its output as input to `g`". So, this line takes the circle from the previous line, and uses it as the last parameter to `extrude_closed`.
264
260
265
261
You could write this same function in a different way without the `|>` operator:
@@ -274,7 +270,7 @@ But we generally find the `|>` operator makes your KCL functions easier to read.
274
270
In this example function, we specified the types of both input parameters and the output type. But the KCL compiler is smart! It's smart enough to infer the types of our parameters and return types even if you don't specify them. So you could have written
275
271
276
272
```kcl
277
-
can_of_beans(radius, height) =
273
+
can_of_beans = (radius, height) =>
278
274
circle(radius)
279
275
|> extrude_closed(material.aluminium, height)
280
276
```
@@ -284,22 +280,23 @@ Here, the KCL compiler:
284
280
* Infers `height` must be a `Distance` because the second parameter of `extrude_closed` is a `Distance`.
285
281
* Infers the function returns a `Solid3d` because it's returning the return value of `extrude_closed`, which is `Solid3d`.
(Note that, as discussed above, this example uses KCL measurement types (e.g. distance) instead of general-purpose number types. This lets you seamlessly interoperate between different units of measurement, like feet and centimeters)
290
+
293
291
This is an expression which evaluates `can_of_beans` with its two input parameters, i.e. radius and height. You can use this anywhere an expression is valid, which currently is just
294
292
1. As an argument to a function
295
293
2. As the body of a function
296
294
297
-
Note that, as discussed above, this example uses KCL measurement types (e.g. distance) instead of general-purpose number types. This lets you seamlessly interoperate between different units of measurement, like feet and centimeters.
298
295
299
296
Some units have aliases, so you could also write
300
297
301
298
```kcl
302
-
can_of_beans((10, Cm), (1, Ft))
299
+
can_of_beans(Cm(10), Ft(1))
303
300
```
304
301
305
302
See the [docs](units) for all units and aliases.
@@ -308,35 +305,69 @@ Every KCL function body is a single expression. If a function body gets really b
308
305
309
306
```kcl
310
307
let
311
-
can_radius = (10, Cm)
312
-
can_height = (1, Ft)
308
+
can_radius = Cm(10)
309
+
can_height = can_radius * 5
313
310
in can_of_beans(can_radius, can_height)
314
311
```
315
312
316
313
The constants you create in `let` are scoped to the let-in expression. The value of the expression is the `in` part. Let-in blocks are a standard piece of notation from functional programming languages (e.g. in [Elm][elm-let-in], [OCaml][ocaml-let-in] and [Haskell][haskell-let-in]) that help make large expressions more readable. We find they make large KCL functions readable too.
317
314
318
-
### Constants
315
+
####Constants
319
316
320
317
KCL doesn't have any mutation or changes, so there aren't any variables. Files contain a number of functions -- that's it.
321
318
322
-
Other languages have named constants and variables. KCL doesn't have variables (because the language describes unchanging geometry and physical characteristics of real-world objects). But it _does_ have named constants. Here's two different ways to declare it:
319
+
Other languages have named constants and variables. KCL doesn't have variables (because the language describes unchanging geometry and physical characteristics of real-world objects). But it _does_ have named constants. Here's how you declare them.
323
320
324
321
```kcl
325
-
my_can = can_of_beans((10, Cm), (1, Ft))
322
+
my_can = can_of_beans(Cm(10), Ft(1))
326
323
```
327
324
328
325
This declares a named constant called `my_can`, which is the result of calling the `can_of_beans` function we defined above. KCL compiler inferred the type, but you can add a type annotation if you want to:
329
326
330
327
```kcl
331
-
my_can: Solid3d = can_of_beans((10, Cm), (1, Ft))
328
+
my_can: Solid3d = can_of_beans(Cm(10), Ft(1))
332
329
```
333
330
334
331
This named constant is actually just syntactic sugar for a function that takes 0 parameters. After all, functions called with the same inputs always return the same value -- they're fully deterministic. So a function with 0 parameters is just a function that always returns a constant value. Or, to simplify: it _is_ a constant value.
335
332
336
333
Without the syntactic sugar, `my_can` could be declared like this:
Note the function signature. Function signatures are always (parameters -> return type), but here we have no parameters, so the function signature just omits them.
340
+
341
+
#### Functions as values
342
+
343
+
Sometimes, functions are parameters to other functions.
344
+
345
+
```kcl
346
+
doubleDistance = (d: Distance) =>
347
+
d * 2
348
+
349
+
doubleAllDistances = (distances: List Distance -> List Distance) =>
350
+
List.map(doubleDistance, distances)
351
+
```
352
+
Here, the `doubleAllDistances` function takes a list of distances and returns a list where all distances are doubled. It does this using the standard library function `List.map`. This takes two parameters:
353
+
354
+
1. A function to call on every element of a list
355
+
2. The list whose elements should be passed into the above function
356
+
357
+
This is neat. You can do a lot with standard library functions like this. However, there's another way to write this code.
358
+
359
+
```kcl
360
+
doubleAllDistances = (distances: List Distance -> List Distance) =>
361
+
List.map((x) => x * 2, distances)
362
+
```
363
+
364
+
In this version, we've replaced the named function `doubleDistance` with an _anonymous function_ (also known as a _closure_). These closures use the same syntax for function declaration -- parameters, then `=>`, then the body. This lets you keep your code a little bit more concise.
365
+
366
+
Again, you don't need to specify function types, but if you want to, you can.
367
+
368
+
```kcl
369
+
doubleAllDistances = (distances: List Distance -> List Distance) =>
370
+
List.map((x: Distance -> Distance) => x * 2, distances)
340
371
```
341
372
342
373
### KCL files
@@ -351,9 +382,9 @@ This invites the question: how do I use this function and why did I write it?
351
382
352
383
There is an open ecosystem of tooling that understands KCL files, and can visualize or analyze the functions contained therein. Here are some examples of what you can do with a KCL function.
353
384
354
-
1. Open it in a KCL viewer. The primary KCL visualizer is built into KittyCAD's KCL live-editing [web app](untitled-app). However, other visualizers exist too. These visualizers help you understand your model and show it to teammates, clients, fans, etc.
385
+
1. Open it in a KCL viewer. The primary KCL visualizer is built into KittyCAD's [modeling app](untitled-app). However, other visualizers exist too. These visualizers help you understand your model and show it to teammates, clients, fans, etc.
355
386
2. Send it to a service like KittyCAD's analysis API. Generally, you send a KCL file, a query type (e.g. "mass" or "cost to print") and your desired unit of measurement (e.g. "kilograms") to that API. Then the API will analyze your KCL, figure out the answer, and convert it to your requested units.
356
-
3.Send it to a 3D printing or prototyping service. They'll accept a KCL file and print out the object returned by your function.
387
+
3.Export it to KittyCAD's GLTF file format, then send that to 3D printing services or manufacturing services. They'll print/manufacture the object your function describes.
357
388
4. Convert it to other, less advanced formats, for your colleagues stuck at legacy companies that use Autodesk.
358
389
359
390
In all these cases, you can choose one or more KCL functions to visualize/analyze/print/export. If you don't specify, the KCL ecosystem generally defaults to looking for a function called `main`. This convention is useful! For example, if a client wants you to build a bookshelf, you can send them a KCL file, where the `main` function outputs the bookshelf. When they open the file in a KCL viewer, they'll see the bookshelf. But they can also open up the sidebar, and look at all the other KCL functions your bookshelf is composed of. Then they can visualize those function separately -- e.g. they might want to drill down to view only the shelf, or the backboard.
@@ -362,6 +393,41 @@ If your function accepts 0 parameters, then it can be visualized easily. But how
362
393
363
394
This is a really powerful way to let consumers customize the goods you've designed before buying or manufacturing them. For example, you might put a design for a cool 3D printed office chair on thingiverse.com which has a function `main(name: Text)`. This function describes a chair with the given name embossed into the back. When a consumer wants to 3D print it, the 3D printing service will let them input their desired name, view the chair with that name embossed, and then order it.
364
395
396
+
## Tests
397
+
Functions are marked as tests by putting the `#[test]` attribute above them. They're run via the KittyCAD CLI or by the test runner built into the KittyCAD modeling app.
398
+
399
+
```kcl
400
+
#[test]
401
+
division_by_1_doesnt_change_number = () =>
402
+
assert_eq(4/1, 4)
403
+
404
+
// Because these functions take no arguments, you can use the syntax sugar
405
+
// from the "Constants" section above.
406
+
#[test]
407
+
division_by_10 =
408
+
let
409
+
expected = 10;
410
+
actual = 100/10;
411
+
in assert_eq(expected, actual)
412
+
```
413
+
The `assert_eq` function will fail the test if the arguments aren't equal. There are similar functions like `assert()` which just checks if its argument is true, or `assert_ne()` which asserts the two are not equal. Tests are run in parallel, because there's no way for two tests to interfere with each other.
414
+
415
+
Test functions cannot take parameters, nor can they return values. So, what if you want to test many different (expected, actual) pairs for your function? Well, you can call `assert_eq` on a list of values. Like this:
416
+
417
+
```kcl
418
+
#[test]
419
+
multiplication_by_zero() =
420
+
let
421
+
n = 100
422
+
inputs = List.range(0, n) // A list of numbers from `0` to `n`.
423
+
expected = List.replicate(0, n) // A list of length `n`, every element is `0`.
424
+
actual = List.map((x) => x * 0, inputs)
425
+
in
426
+
List.map2(assert_eq, actual, expected)
427
+
```
428
+
Here, the function `List.map2` is a lot like `List.map` except it has _two_ input lists. Its function argument takes an element from each list, instead of just from one list. So, it takes a function of type `(a, b) => c`, a `List a` and a `List b` and passes them into the function, element by element, creating a `List c`.
429
+
430
+
So, used here, it takes the input lists `actual` and `expected`, then passes an element from each into `assert_eq`.
0 commit comments