From bc2df73487ea13d588313023fa3b979a8df73d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Mon, 30 Jun 2025 23:54:50 +0200 Subject: [PATCH] documentation updates to reflect removal of the `let` keyword also some general cleanups and improvements --- docs/rkgk.dj | 150 +++++++++++++++++++++++++++++++++---------------- docs/system.dj | 42 +++++++------- 2 files changed, 125 insertions(+), 67 deletions(-) diff --git a/docs/rkgk.dj b/docs/rkgk.dj index cfceca3..7335554 100644 --- a/docs/rkgk.dj +++ b/docs/rkgk.dj @@ -317,7 +317,7 @@ Once you define a name, its associated data stays the same throughout the entire So we can define `thickness` to be `4`, and then use it in our scribbles. ```haku -thickness = 4 +thickness: 4 withDotter \d -> [ @@ -327,10 +327,11 @@ withDotter \d -> ] ``` -`name = data` is a special operator in haku that tells the language "whenever we say `name`, we mean `data`." +`name: data` is a special operator in haku that means "whenever we say `name`, we mean `data`." +We call this operator _def_, short for _definition_. We cannot use it in arbitrary places in our program, because it wouldn't make sense. -What does it mean to have a stroke whose thickness is `meow = 5`? +What does it mean to have a stroke whose thickness is `meow: 5`? To keep a consistent program structure, haku also forces all your defs to appear _before_ your scribble. You can think of the defs as a list of ingredients for the final scribble. @@ -348,8 +349,8 @@ We'll get to why soon! Anyways, we can likewise replace our `2` constants with a def: ```haku -thickness = 4 -xOffset = 2 +thickness: 4 +xOffset: 2 withDotter \d -> [ @@ -380,9 +381,9 @@ But now there's a problem. If we change our `thickness` back to `8`, our points will overlap! ```haku -thickness = 8 - --- -xOffset = 2 +thickness: 8 + --- +xOffset: 2 withDotter \d -> [ @@ -394,9 +395,9 @@ withDotter \d -> So we'll make our `xOffset` calculated dynamically from the `thickness`, to not have to update it every time. ```haku -thickness = 8 -xOffset = thickness / 2 - ------------- +thickness: 8 +xOffset: thickness / 2 + ------------- withDotter \d -> [ @@ -484,7 +485,7 @@ That'll need fixing! Either way, let's define a function that'll make us those circles! ```haku -splat = \d, radius -> +splat: \d, radius -> fill #0001 (circle (d To) radius) withDotter \d -> @@ -534,7 +535,7 @@ haku limits the use of overloading to system functions for simplicity---adding o Since these transparent circles are so much easier to draw now, let's make a few more of them! ```haku -splat = \d, radius -> +splat: \d, radius -> fill #0001 (circle (d To) radius) withDotter \d -> @@ -580,10 +581,10 @@ Until some threshold is reached, in which case we just make a single circle. The first part is easy to do: haku allows us to define a function that calls itself without making any fuss. ```haku -splat = \d, radius -> +splat: \d, radius -> fill #0001 (circle (d To) radius) -airbrush = \d, size -> +airbrush: \d, size -> [ splat d size airbrush d (size - 8) @@ -616,9 +617,9 @@ We call this act of switching execution paths _branching_. Try this out---change the `radius`, and observe how your brush changes color once you set it beyond 16: ```haku -radius = 8 +radius: 8 -color = +color: if (radius < 16) #00F else @@ -641,10 +642,10 @@ An `if` only calculates the argument it needs to produce the result. This allows us to use it to prevent unbounded recursion in our `airbrush` example. ```haku -splat = \d, radius -> +splat: \d, radius -> fill #0001 (circle (d To) radius) -airbrush = \d, size -> +airbrush: \d, size -> if (size > 0) [ splat d size @@ -667,10 +668,10 @@ But the airbrush still looks super primitive. Let's try increasing the fidelity by doing smaller steps! ```haku -splat = \d, radius -> +splat: \d, radius -> fill #0001 (circle (d To) radius) -airbrush = \d, size -> +airbrush: \d, size -> if (size > 0) [ splat d size @@ -687,11 +688,11 @@ withDotter \d -> Well... sure, that's just a black blob with a slight gradient on the outer edge, so let's decrease the opacity. ```haku -splat = \d, radius -> +splat: \d, radius -> fill #00000004 (circle (d To) radius) --------- -airbrush = \d, size -> +airbrush: \d, size -> if (size > 0) [ splat d size @@ -740,7 +741,7 @@ Most commonly, colors are blended using _linear interpolation_---which is essent Mathematically, linear interpolation is defined using this formula: ```haku -lerp = \a, b, t -> +lerp: \a, b, t -> a + (b - a) * t ``` @@ -806,12 +807,30 @@ For now you'll have to construct your brushes with this in mind. ### And more limits -There are more limits on top of this, which stem from haku's design. -Since it's running _your_ code on _my_ server, it has some arbitrary limits set to prevent it from causing much harm. +There are more limits on top of this, which stem from rakugaki's design. -haku code cannot be too long, and it cannot execute too long. -It cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once. -There are also memory usage limits on "heavyweight" data, such as functions or lists. +Recall that rakugaki is multiplayer! +You can draw with your friends. +But, your friends may not have a computer as good as yours. +So to keep the experience fair, haku sets some limits on brush code. + +A brush cannot be too long, and it cannot execute too long. +It also cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once. + +If you ever brush (ha ha) up against these limits, you'll see colorful bars appear beside the brush preview in the bottom right corner. +These bars show you how much you're nearing the limits! + +Try adding this line to your brush: + +```haku +r: range 1 30000 +``` + +`range 1 30000` generates a list of integers between 1 and 30000, without having you write them out one by one. +But we can also see that it consumes a bunch of _fuel_ (units of execution time), and _a lot_ of memory---almost all, in fact! + +Code size is harder to run up against, because it requires writing a pretty huge amount of characters into the editor. +Feel free to try it out yourself---try writing out some really long lists by hand, and see what happens! ## Reticles @@ -839,17 +858,17 @@ This allows you to _animate_ your brushes over time! For example, this brush draws a rainbow. ```haku -colorCurve = \n -> +colorCurve: \n -> abs (cos n) -pi = 3.14159265 -l = 0.1 -- wavelength +pi: 3.14159265 +l: 0.1 -- wavelength withDotter \d -> - let r = colorCurve (d Num * l) - let g = colorCurve (d Num * l + pi/3) - let b = colorCurve (d Num * l + 2*pi/3) - let color = rgba r g b 1 + r = colorCurve (d Num * l) + g = colorCurve (d Num * l + pi/3) + b = colorCurve (d Num * l + 2*pi/3) + color = rgba r g b 1 stroke 8 color (line (d From) (d To)) ``` @@ -859,28 +878,28 @@ Currently, `withDotter` is the only reticle available in rakugaki, and it cannot In the future rakugaki might get reticles that let you select lines, rectangles, ellipses, curves... but today is not that day. -### What's that, `let`? +### What's that, `=`? I mentioned before that you cannot have defs inside functions. -What you _can_ have though, is `let`s, which define _variables_. +What you _can_ have though, is _variables_, defined with `name = data`. Unlike defs, which are constant and cannot vary, variables' values can depend on function parameters---and a function can be called with a different set of parameters each time, thus making them variable! -A `let` always takes the following form. +A variable always takes the following form. ```haku -let name = value +name = value then ``` It's very similar to a def, with one major difference. -Because a `let` by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result. -The magic is that this continuing expression can refer to the `name` we had previously assigned in the `let` expression. +Because a variable by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result. +The magic is that this continuing expression can refer to the `name` we had previously assigned in the `name = value` expression. ::: aside -Here's a bit of trivia: the variable defined by a `let` is exactly the same as a function parameter. -The `let` above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result. +Here's a bit of trivia: variables are exactly the same as function parameters! +The `name = value` expression above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result. ```haku (\name -> then) value @@ -893,15 +912,15 @@ That's right. haku is a cute little Haskell for artists. ::: -`let`s aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces! +Variables aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces! Compare the above version of the rainbow brush to this version, where all the `let`s are written inline: ```haku -colorCurve = \n -> +colorCurve: \n -> abs (cos n) -pi = 3.14159265 -l = 0.1 -- wavelength +pi: 3.14159265 +l: 0.1 -- wavelength withDotter \d -> stroke 8 (rgba (colorCurve (d Num * l)) (colorCurve (d Num * l + pi/3)) (colorCurve (d Num * l + 2*pi/3)) 1) (line (d From) (d To)) @@ -912,6 +931,41 @@ That's one hard to read beast of a `stroke`! Generally, if a line is so long it wraps around rakugaki's narrow little text editor, it's probably a good idea to split it into variables. +### So... sometimes it's `:`, sometimes it's `=`, help, I'm confused! + +If you're having trouble understanding when to use `:` and when to use `=`, here's a short version: + +- `:` defines a name across your whole program. +- `=` defines a name that's only visible on the next line (more or less.) + +Since `:` defines a name accessible from your whole program, it cannot access function parameters---which are temporary and local. +The opposite of "accessible in the whole program"! + +As for `=`, I said it defines a name that's only visible on the next line "_more or less_", because technically the line that follows can actually be broken up into multiple lines, as would be the case with e.g. an `if`: + +```haku +color: #000 +thickness: 4 +length: 5 +duty: 0.5 + +or_: \a, b -> -- haku doesn't have a boolean OR yet... + if (a) a + else b + +withDotter \d -> + visible = mod (d Num) length < length * duty + if (visible) + -- this is more than one line, and `visible` can still be used here! + stroke thickness color (line (d From) (d To)) + else + () -- ...and here, too! +``` + +Another thing: as shown in the examples above, you can chain multiple `=` expressions together, which also breaks this "next line" rule of thumb. +But it should still help in building an intuition and spotting the patterns! + + ## Have fun With that said, I hope you can have fun with rakugaki despite it being in its infancy! diff --git a/docs/system.dj b/docs/system.dj index 03a2a4b..1b9f474 100644 --- a/docs/system.dj +++ b/docs/system.dj @@ -25,8 +25,6 @@ Operators may have one or two arguments, where one argument corresponds to a pre Note that this documentation lists a unary and binary operator of the same spelling as _two separate functions_, not overloads of a single function. The argument name usually does not matter when calling the function - it is only used for documentation purposes. -The one exception is arguments called `...`, which signify that zero or more arguments can be passed to the function at that position. -(Currently there are no functions that accept any number of arguments, though.) The argument _type_ however is important. If you try to use a function with the wrong type of value as its argument, it will fail with an error. @@ -567,7 +565,7 @@ For example, consider multiplicatively blending two colors. ```haku -- This is how you can multiply two colors together. -- Note that the `*` operator works for colors, so you don't need to define this in your brushes. -mulRgba = \a, b -> +mulRgba: \a, b -> rgba (rgbaR a * rgbaR b) (rgbaG a * rgbaG b) (rgbaB a * rgbaB b) (rgbaA a * rgbaA b) ``` @@ -575,11 +573,11 @@ If haku represented colors using an 8-bit `0` to `255` range instead, to multipl ```haku -- NOTE: This example does NOT work correctly. -mulRgba = \a, b -> - let red = (rgbaR a * rgbaR b) / 255 - let green = (rgbaG a * rgbaG b) / 255 - let blue = (rgbaB a * rgbaB b) / 255 - let alpha = (rgbaA a * rgbaA b) / 255 +mulRgba: \a, b -> + red = (rgbaR a * rgbaR b) / 255 + green = (rgbaG a * rgbaG b) / 255 + blue = (rgbaB a * rgbaB b) / 255 + alpha = (rgbaA a * rgbaA b) / 255 rgba red green blue alpha ``` @@ -683,32 +681,38 @@ Some of these operations may be a bit confusing, so here are some examples. ```haku -- To add two to all elements in a list: -list = range 1 4 -- [1, 2, 3, 4] -twoAdded = map list \x -> +list: range 1 4 -- [1, 2, 3, 4] +twoAdded: map list \x -> x + 2 +-- [3, 4, 5, 6] ``` ```haku -- To filter out only even numbers in a list: -list = range 1 10 -isEven = \x -> mod x 2 == 0 -onlyEven = filter list isEven +list: range 1 10 -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +isEven: \x -> mod x 2 == 0 +onlyEven: filter list isEven +-- [2, 4, 6, 8, 10] ``` ```haku -- To sum all the numbers in a list: -list = [1, 3, 10, 2, 30, 4, 1] -sum = reduce list 0 \acc, value -> acc + value +list: [1, 3, 10, 2, 30, 4, 1] +sum: reduce list 0 \acc, value -> acc + value +-- 51 ``` ```haku -- To flatten a singly-nested list: -list = [[1, 2], [3, 4], [5, 6]] -flatList = flatten list -- [1, 2, 3, 4, 5, 6] +list: [[1, 2], [3, 4], [5, 6]] +flatList: flatten list -- [1, 2, 3, 4, 5, 6] -- Note that this only applies to a single level of nesting: -deepList = [[[1, 2, 3, 4]]] -lessDeepList = flatten deepList -- [[1, 2, 3, 4]] +deepList: [[[1, 2, 3, 4]]] +lessDeepList: flatten deepList -- [[1, 2, 3, 4]] + +-- This can be used to join lists together without nesting: +join: \a, b -> flatten [a, b] ```