|
| 1 | +# Syntactic improvements |
| 2 | + |
| 3 | +This doc proposes a bunch of improvements with the goal of a simple, cleaner, more intuitive syntax. Note that some of these changes go beyond just syntax changes though the effect is a cleaner syntax. |
| 4 | + |
| 5 | +Currently, the first impression of KCL is poor - it feels very noisey with a lot of punctuation and symbols with non-obvious meanings and which are hard to search for. |
| 6 | + |
| 7 | +For example, consider this sample |
| 8 | + |
| 9 | +``` |
| 10 | +lugHoles = startSketchOn(lugBase, 'END') |
| 11 | + |> circle({ |
| 12 | + center: [lugSpacing / 2, 0], |
| 13 | + radius: 16 * mm() / 2 |
| 14 | + }, %) |
| 15 | + |> patternCircular2d({ |
| 16 | + arcDegrees: 360, |
| 17 | + center: [0, 0], |
| 18 | + instances: lugCount, |
| 19 | + rotateDuplicates: true |
| 20 | + }, %) |
| 21 | + |> extrude(-wheelWidth / 20, %) |
| 22 | +``` |
| 23 | + |
| 24 | +A newcomer might question: |
| 25 | + |
| 26 | +- What does `|>` do? |
| 27 | +- What does `'END'` mean? Why is it quoted? |
| 28 | +- Why doe I need `{}` in `Circle` but not `startSketchOn` |
| 29 | +- What is `%` for? |
| 30 | +- Why does `mm()` have the `()` and why is it multiplying the number |
| 31 | +- What is `[]` for when using `center` |
| 32 | + |
| 33 | +In other cases there are also `$` to declare tags, function declarations which are noisey (e.g., `fn spoke = (spokeGap, spokeAngle, spokeThickness) => { ... }`) - why is `=` and `=>` needed? Why does this look different to the std lib docs (e.g., `angledLineOfXLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator)` - note no `=`, `: type` on arguments, and note that the `{}` has different semantics to those in the snippet). Points which are a key primitive are sometimes declared as `{ x: 1, y: 0, z: 0 }` and sometimes as `[0, 1, 2]`, and so forth. |
| 34 | + |
| 35 | +This doc proposes some small changes. Other related changes are suggested in other docs: |
| 36 | + |
| 37 | +- Changes to pipeline syntax and removing `%` in [pipelines and tags](pipelines.md) |
| 38 | +- Using `=` instead of `:` to initialise object fields in [type system](types.md) |
| 39 | +- Leading underscores (including just an underscore) are variables which can't be used in [construction geometry](show.md) |
| 40 | + |
| 41 | +## Function declarations |
| 42 | + |
| 43 | +Remove `=` and `=>`. E.g., `fn spoke = (spokeGap, spokeAngle, spokeThickness) => { ... }` becomes `fn spoke(spokeGap, spokeAngle, spokeThickness) { ... }`. |
| 44 | + |
| 45 | +See [type system](types.md) for adding type annotations. See [functions](functions.md) for changes to organising and calling functions. |
| 46 | + |
| 47 | +## Remove anonymous objects |
| 48 | + |
| 49 | +The main use case was for function arguments. In general, named objects are better. |
| 50 | + |
| 51 | +## Point and vector types and constructors |
| 52 | + |
| 53 | +We introduce `Point2D`, `Point3D`, `Vec2D`, and `Vec3D` objects for absolute and relative points in space, respectively. |
| 54 | + |
| 55 | +The constructor functions `pt` and `vec` produce these objects, depending on the number of arguments. E.g., `pt(0, 0, 0)` produces an object `Point3D { x = 0, y = 0, z = 0 }`. This facilitates access using `foo.x` or `foo['x']`, the latter useful for iterating over coordinates. |
| 56 | + |
| 57 | +Later work: where the type is unambiguous (see [type system](types.md)), the user can just write `(0, 0, 0)` and KCL will infer whether to use `pt` or `vec`. I'm not sure if this desugaring should be made extensible or limited to the point and vector types. |
| 58 | + |
| 59 | +## Use constants instead of magic strings |
| 60 | + |
| 61 | +E.g., `XY` instead of `'XY'`. |
| 62 | + |
| 63 | +Remove use of magic strings such as `END` for identifying geometry (see [tagging](TODO) for a proposed alternative). |
| 64 | + |
| 65 | +## Example |
| 66 | + |
| 67 | +Repeating and combining the initial examples: |
| 68 | + |
| 69 | +``` |
| 70 | +fn spoke = (spokeGap, spokeAngle, spokeThickness) => { |
| 71 | + ... |
| 72 | +} |
| 73 | +
|
| 74 | +lugHoles = startSketchOn(lugBase, 'END') |
| 75 | + |> circle({ |
| 76 | + center: [lugSpacing / 2, 0], |
| 77 | + radius: 16 * mm() / 2 |
| 78 | + }, %) |
| 79 | + |> patternCircular2d({ |
| 80 | + arcDegrees: 360, |
| 81 | + center: [0, 0], |
| 82 | + instances: lugCount, |
| 83 | + rotateDuplicates: true |
| 84 | + }, %) |
| 85 | + |> extrude(-wheelWidth / 20, %) |
| 86 | +``` |
| 87 | + |
| 88 | +would become |
| 89 | + |
| 90 | +``` |
| 91 | +fn spoke(spokeGap, spokeAngle, spokeThickness) { |
| 92 | + ... |
| 93 | +} |
| 94 | +
|
| 95 | +lugHoles = startSketchOn(lugBase.faces.end) |
| 96 | + |> circle( |
| 97 | + center = (lugSpacing / 2, 0), |
| 98 | + radius = 16mm / 2, |
| 99 | + ) |
| 100 | + |> patternCircular2d( |
| 101 | + arcDegrees = 360, |
| 102 | + center = (0, 0), |
| 103 | + instances = lugCount, |
| 104 | + rotateDuplicates = true |
| 105 | + ) |
| 106 | + |> extrude(-wheelWidth / 20) |
| 107 | +``` |
| 108 | + |
| 109 | +Notes |
| 110 | + |
| 111 | +- `lugBase.faces.end` is a strawman for finding geometry, requires work |
| 112 | +- `16mm` relies on work on units of measure, assuming mm is the default unit for the project, just `16` should work |
| 113 | +- `center = (0, 0)` could also be written as `center = pt(0, 0)` |
| 114 | +- fully annotated with types, the function might look like `fn spoke(spokeGap: Number, spokeAngle: Number, spokeThickness: Number): Solid` or `fn spoke(spokeGap: num, spokeAngle: num, spokeThickness: num): Solid` or `fn spoke(spokeGap: Number(1d), spokeAngle: Number(angle), spokeThickness: Number(1d)): Solid`, we wouldn't need any other type annotations in the language. |
| 115 | + |
| 116 | +I believe this keeps the spirit and advantages of KCL while making the syntax friendlier and more attractive. |
0 commit comments