# Introduction to rakugaki Welcome to rakugaki! I hope you've been having fun fiddling with the app so far. Since you're reading this, this must mean you're interested in learning more about how to use it! ::: aside I'm [liquidex](https://liquidex.house), and I'm the creator of rakugaki! I'll be your host throughout the manual. You'll find my notes scattered on the side like this. Or tangled into text, if you're reading this on a mobile device. (Listen, narrow screens are hard. I hope these aren't gonna be too annoying.) ::: ## The wall In case you edited anything in the input box on the right, paste the following text into it before continuing: ```haku -- This is your brush. -- Try playing around with the numbers, -- and see what happens! withDotter \d -> stroke 8 #000 (d To) ``` rakugaki is a drawing program for digital scribbles and other pieces of art. Unlike most drawing programs, rakugaki offers an infinite canvas, which we call _the wall._ You can draw on the wall by *holding down your left mouse button and dragging the mouse across the screen.* You can likewise move your viewport by *holding down your middle or right mouse button, and dragging the mouse across the screen.* You can also zoom in and out by *scrolling.* Try to familiarize yourself with these controls by drawing some stuff! You can also invite friends to play around with, by sending them your wall's URL. ## Your brush What sets rakugaki apart from other drawing apps is that all drawing is done via a tiny computer program called _the brush._ Most drawing programs offer very customizable brushes, but rakugaki is unique in that the brushes are computer programs! ::: aside The name _rakugaki_ comes from the Japanese word 落書き, which roughly translates to _scribbles_! Japanese artists also sometimes use the abbreviation rkgk, which is where the website _rkgk.app_ comes from. ::: The task of a brush is to take the strokes you make on the wall, and turn them into instructions on what should be drawn on the wall. We call these instructions _scribbles._ You can edit your brush in the _brush editor_, which can be found in the top right corner of your screen. Try fiddling with the code a bit and see what happens! ## The code Brushes are written in rakugaki's custom programming language called _haku._ haku belongs to a family of programming languages known as _functional_ programming languages. In these languages, instead of giving the computer direct instructions on what to do, we instead _declare_ what we'd like the computer to do by using various forms of data. haku treats all sorts of things as data. Numbers are data, shapes are data, colors are data, and of course, scribbles are also data. The task of a haku program is to manipulate data to produce a _single scribble._ Theoretically, this would mean brushes are very limited. After all, if we're only limited to drawing single scribbles, wouldn't that mean a brush can only draw a single shape? But the magical part is that you can _compose scribbles together._ If you want to draw multiple scribbles, you can wrap them into a list, which we denote with square brackets `[]`: ```haku -- Draw two colorful dots instead of one! withDotter \d -> [ stroke 8 #F00 (d To + vec 4 0) stroke 8 #00F (d To + vec (-4) 0) ] ``` ::: aside haku uses the syntax `-- OwO` for _comments_---human-readable pieces of text that are ignored by the compiler. A comment begins with `--`, and ends at the end of a line. They're pretty useful for making your code more understandable! After all, we don't speak programming languages natively. ::: And what's even crazier is that you can compose lists _further_---you can make a list of lists, and rakugaki will be happy with that! It'll draw the first inner list, which contains two scribbles, and then it'll draw the second inner list, which contains two scribbles. ```haku withDotter \d -> [ [ stroke 8 #F00 (d To + vec 4 0) stroke 8 #00F (d To + vec (-4) 0) ] [ stroke 8 #FF0 (d To + vec 0 4) stroke 8 #0FF (d To + vec 0 (-4)) ] ] ``` ::: aside Another weird thing: when negating a number, you have to put it in parentheses. This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing! In this case, it will always choose the 2nd interpretation---vec minus four. So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses. ::: This might seem useless, but it's a really useful property in computer programs. It essentially means you can snap pieces together like Lego bricks! One thing that comes up here however is _what order_ rakugaki will draw the scribbles in. After all, the pixels produced by scribbles may partially or even fully overlap. Put simply, rakugaki will always draw things from first to last. Therefore, scribbles that are listed later will be drawn on top of scribbles that are listed earlier. Anyways! ## So what's this ceremony with all the words and symbols? Recall that super simple brush from before... ```haku withDotter \d -> stroke 8 #000 (d To) ``` This reads as "given a dotter, output a stroke that's 8 pixels wide, has the color `#000`, and is drawn at the dotter's `To` coordinates." All these symbols are very meaningful to haku. If you reorder or remove any one of them, your brush isn't going to work! - Reading from left to right, we start with `withDotter`. We can't draw without knowing _where_ to draw, and the `withDotter` incantation lets us ask the UI for that. - Then, `\d ->` lets us name the data we get back from the UI. `d` ends up containing a few useful properties, but the most useful one for us is `To`, which contains the current mouse position (where *`To`* draw). - We'll get to what all these `\` and `->` sigils mean to haku later! - On the next line we have a `stroke`. `stroke` is a _function_---a recipe for producing data!\ haku has [many such built-in recipes](/docs/system.html). `stroke` is one of them. - Each function requires some amount of _arguments_. These are the ingredients that will be used to produce our piece of data.\ In haku, we specify the arguments to a function by listing them on the same line as the function's name, one after another, separated by spaces. - The first ingredient we need for a `stroke` is its _thickness_. This is a plain old number, counted in pixels. We say we want a stroke of thickness `8`. - The second ingredient is the stroke's _color_. haku uses the familiar hex code syntax `#RRGGBB` for colors, but it allows writing `#RGB` for brevity---`#08F` is the same as `#0088FF`.\ You can also specify an alpha channel, for transparent colors---`#RRGGBBAA`, or `#RGBA`. - The third ingredient is the stroke's _position_. Positions in haku are represented using mathematical _vectors_, which, when broken down into pieces, are just lists of some numbers. haku vectors however are a little more constrained, because they always contain _four_ numbers---this makes them _four-dimensional_. We call these four numbers X, Y, Z, and W respectively. Four is a useful number of dimensions to have, because it lets us do 3D math---which technically isn't built into haku, but if you want it, it's there. For most practical purposes, we'll only be using the first _two_ of the four dimensions though---X and Y. This is because the wall is a 2D space---it's a flat surface with no depth. It's important to know though that vectors don't mean much _by themselves_---rakugaki just chooses them to represent points on the wall, but in a flat 2D space, all points need to be relative to some _origin_---the vector `(0, 0)`. In brushes, this position is at the tip of the mouse cursor. Positive X coordinates go rightwards, and positive Y coordinates go downwards. Likewise, negative X coordinates go leftwards, and negative Y coordinates go upwards. --- Vectors in haku are obtained with another function---`vec`---though we don't use it in the basic example, because `d To` already is a vector. Vectors support all the usual math operators though, so if we wanted to, we could, for example, add a vector to `d To`, thus moving the position of the dot relative to the mouse cursor: ```haku withDotter \d -> stroke 8 #000 (d To + vec 10 0) -- moved 10 pixels rightwards ``` Also note how the `d To` expression is parenthesized. This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want! Anyways, with all that, we let haku mix all the ingredients together, and get a black dot under the cursor. ```haku withDotter \d -> stroke 8 #000 (d To) ``` Nice! ## Shapes Of course, life would be boring if singular points were all we could ever draw. So to spice things up, haku has a few shapes you can choose from! Recall that 3rd argument to `stroke`. We can actually pass any arbitrary shape to it, and haku will outline it for us. The experience of drawing with that example brush is pretty crap, because it can draw dots that don't connect with each other at all. Let's fix that by drawing a `line` instead! ```haku withDotter \d -> stroke 8 #000 (line (d From) (d To)) ``` We replace the singular position `d To` with a `line`. `line` expects two arguments, which are vectors defining the line's start and end points. For the starting position we use a _different_ property of `d`, which is `From`---this is the _previous_ value of `To`, which allows us to draw a continuous line. ::: aside In haku, by adding thickness to a point, it becomes a circle. In theory it could also become a square... but if line caps are rounded, they connect together much more nicely! ::: haku also supports other kinds of shapes: circles and rectangles. ```haku withDotter \d -> [ stroke 8 #F00 (circle (d To + vec (-16) 0) 16) stroke 8 #00F (rect (d To + vec 0 (-16)) 32 32) ] ``` - `circle`s are made up of an X position, Y position, and radius. - `rect`s are made up of the (X and Y) position of their top-left corner, and a size (width and height).\ Our example produces a square, because the rectangle's width and height are equal! ## Programming in haku So far we've been using haku solely to describe data. But if describing data was all we ever wanted, we could've just used any ol' drawing program's brush engine! Remember that example from before? ```haku withDotter \d -> [ stroke 8 #F00 (d To + vec 4 0) stroke 8 #00F (d To + vec (-4) 0) ] ``` It has quite a bit of repetition in it. If we wanted to change the size of the points, we'd need to first update the stroke thickness... ```haku withDotter \d -> [ stroke 4 #F00 (d To + vec 4 0) stroke 4 #00F (d To + vec (-4) 0) --- ] ``` ...twice of course, because we have two scribbles. But now there's a gap between our points! So we also have to update their positions. ```haku [ stroke 4 #F00 (d To + vec 2 0) --- stroke 4 #00F (d To + vec (-2) 0) -- ] ``` Now imagine if we had four of those points. That's quite a lot of copy-pasting for such a simple thing! Luckily, haku has a solution for this: we can give a _name_ to a piece of data by using a _definition_, and then refer to that piece of data using that name we chose. Definitions are called _defs_ in short. ::: aside I'm purposefully avoiding the name _variable_ here. Definitions are *not* variables, because they cannot vary. Once you define a name, its associated data stays the same throughout the entire brush! ::: So we can define `thickness` to be `4`, and then use it in our scribbles. ```haku thickness = 4 withDotter \d -> [ stroke thickness #F00 (d To + vec 2 0) stroke thickness #00F (d To + vec (-2) 0) --------- ] ``` `name = data` is a special operator in haku that tells the language "whenever we say `name`, we mean `data`." 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`? 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. Reading the ingredients can give you context as to what you're gonna be cooking, so it's useful to have them first! If you find this hard to grasp, just know that _defs usually go before `withDotter`_. ::: aside We cannot refer to `d` from defs, because `d` is only visible on the `withDotter` line and below. We'll get to why soon! ::: Anyways, we can likewise replace our `2` constants with a def: ```haku thickness = 4 xOffset = 2 withDotter \d -> [ stroke thickness #F00 (d To + vec xOffset 0) stroke thickness #00F (d To + vec (-xOffset) 0) --------- ] ``` Note how in haku, names may not contain spaces. We cannot have a variable called `x offset`, so we choose `xOffset` instead. This naming convention is known as `camelCase`, and is used everywhere throughout the haku system library. ::: aside Of note is that haku names also cannot start with an uppercase letter. Uppercase names are special values we call _tags_. Tags are values which represent names. For example, the `To` in `d To` is a tag. It represents the name of the piece of data we're extracting from `d`. There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood. ::: But now there's a problem. If we change our `thickness` back to `8`, our points will overlap! ```haku thickness = 8 --- xOffset = 2 withDotter \d -> [ stroke thickness #F00 (d To + vec xOffset 0) stroke thickness #00F (d To + vec (-xOffset) 0) ] ``` 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 ------------- withDotter \d -> [ stroke thickness #F00 (d To + vec xOffset 0) stroke thickness #00F (d To + vec (-xOffset) 0) ] ``` Try playing with the `thickness` now! You'll notice the points always stay an equal distance apart, without any overlap. ## An airbrush for our digital wall So far we've only been dealing with strokes. So why not switch it up a little and _fill in_ a shape? ```haku withDotter \d -> fill #000 (circle (d To) 16) ``` How about... some transparency? Recall that colors can have an alpha component, so let's try using that! ```haku withDotter \d -> fill #0001 (circle (d To) 16) ``` If you play around with this brush, you'll notice how the circles blend together really nicely. That's the power of Alpha! Now let's see what happens if we draw two such circles on top of each other---one bigger, one smaller. ```haku withDotter \d -> [ fill #0001 (circle (d To) 16) fill #0001 (circle (d To) 32) ] ``` How about four? ```haku withDotter \d -> [ fill #0001 (circle (d To) 8) fill #0001 (circle (d To) 16) fill #0001 (circle (d To) 24) fill #0001 (circle (d To) 32) ] ``` Okay, this is starting to look interesting, but it's also getting super unwieldy code-wise! I mean, just look at these repeated lines... Doesn't that remind you of that previous code example? Could there be some way to cleverly use defs to make it more readable? ...Well, the problem here's that the values vary, while defs are constant! So no def in the world is going to save us here. But what if we could def some _code_, and then weave our changing values into that? Or, maybe in other words, list a bunch of values, and then transform them into something else? ...We already have a tool for that! ### Defining our own functions Just like haku defines a set of _system_ functions, we can create and define _our own_ functions too! In haku, functions are data like anything else. We create them using the syntax `\x -> y`. Because they are data like anything else, we can give them names with defs, or we can pass them into other functions for further manipulation. ::: aside Actually, system functions are kind of special. For performance reasons, (and because I was hasty to get a working prototype,) they cannot be passed as arguments to other functions. That'll need fixing! ::: Either way, let's define a function that'll make us those circles! ```haku splat = \d, radius -> fill #0001 (circle (d To) radius) withDotter \d -> [ splat d 8 splat d 16 splat d 24 splat d 32 ] ``` That's a lot nicer, isn't it---a template for our circles is neatly defined in a single place, and all we do is reuse it, each time with a different `radius`. To dismantle that weird `\` syntax... - The character `\` is a short way of saying _function of_. It's supposed to resemble the Greek letter λ, but be easier to type on our antiquated ASCII keyboards. - After `\`, we have a list of _parameters_. Parameters are the names we give to a function's arguments---for a function call `splat 8`, we need the function to have a name for that `8` datum that gets passed to it. Otherwise it has no way to use it! A function can have an arbitrary number of parameters listed, separated by commas, and that many parameters _must_ be passed to it. Otherwise your brush will fail with an error! - Note how also pass `d` into `splat`. If you're a keen-eyed observer, you will have already noticed that the `\d ->` after `withDotter` is a function, too! And the values a function uses, such as `d To`, have to come from somewhere. - And lastly, after an arrow `->`, we have the function's result. Note that a function can only have _one_ result, just like a brush can only have one scribble. ::: aside One interesting thing you may have noticed with parameters, is that some system functions can accept varying numbers of them. Such as `vec`, which can accept from zero to four. This is called _function overloading_ and is somewhat common among programming languages. It is also kind of controversial, because if a function if overloaded to do vastly different things depending on the number or type of data that is given to it, it can become quite hard to predict what it'll really do! haku limits the use of overloading to system functions for simplicity---adding overloading would require introducing extra syntax, which would make the language harder to grok fully. ::: Since these transparent circles are so much easier to draw now, let's make a few more of them! ```haku splat = \d, radius -> fill #0001 (circle (d To) radius) withDotter \d -> [ splat d 8 splat d 16 splat d 24 splat d 32 splat d 40 splat d 48 splat d 56 splat d 64 ] ``` Okay, I'll admit this is getting kind of dumb. We have to make _a lot_ of these circles, and we're still repeating ourselves. There's less to repeat, but my brain can quickly recall only so many increments of 8. ::: aside Seriously, 64 is my limit. ::: I wonder if there's any way we could automate this? ### The Ouroboros You know the drill by now. We're programmers, we're lazy creatures. Anything that can be automated, we'll automate. But there doesn't seem to be an obvious way to repeat a bunch of values like this, no? Well, there isn't. At least not in a continuous list like that, yet. But remember how lists can nest? What we _could_ do is define a function that constructs a list out of a circle, and then a call back to _itself_, which will then construct another list out of a circle and a call back to itself, so on and so forth... 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 -> fill #0001 (circle (d To) radius) airbrush = \d, size -> [ splat d size airbrush d (size - 8) ] withDotter \d -> airbrush d 64 ``` But... ``` an exception occurred: too much recursion ``` That won't work! haku doesn't let our code run indefinitely, and that's precisely what would happen in this case. Also, it used an important word in that error message: *recursion.* This is what we call the act of a function calling itself. Sometimes people say that a function calls itself _recursively_, which sounds redundant, but it clarifies it's to achieve _iteration_---the act of executing the same code repeatedly, over and over again. Anyways, we need some way to make the function _stop_ calling itself after some time. For that, there's another piece of haku magic we can use: `if`. `if` will execute a bit of code and pass on its result if a condition is found to be true. Otherwise, it will execute a different bit of code. 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 color = if (radius < 16) #00F else #F00 withDotter \d -> fill color (circle (d To) radius) ``` - `<` is a function that produces `true` if the second argument is a smaller number than the first argument. Truth and falsehood are data too, and are represented with the values `True` and `False`. - We need three arguments to execute an `if`: the condition, the data to use when the condition is `True`, and the data to use when the condition is `False`. What's magical about an `if` is that _only one branch is executed_. In a function call, all arguments will always be calculated. 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 -> fill #0001 (circle (d To) radius) airbrush = \d, size -> if (size > 0) [ splat d size airbrush d (size - 8) ] else [] withDotter \d -> airbrush d 64 ``` Neat! Our brush now looks cleaner than ever. All we have to do is specify the size, and the code does all the magic for us! Obviously, it's not really shorter than what we started with when we were listing all the circles manually, but the beauty is that we can control all the parameters trivially, by editing single numbers---no need for copy-pasting stuff into hellishly long lists. But the airbrush still looks super primitive. Let's try increasing the fidelity by doing smaller steps! ```haku splat = \d, radius -> fill #0001 (circle (d To) radius) airbrush = \d, size -> if (size > 0) [ splat d size airbrush d (size - 1) --- ] else [] withDotter \d -> airbrush d 64 ``` 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 -> fill #00000004 (circle (d To) radius) --------- airbrush = \d, size -> if (size > 0) [ splat d size airbrush d (size - 1) ] else [] withDotter \d -> airbrush d 64 ``` Looks good as a single dot, but if you try drawing with it... it's gray?? ## Limits of the wall Unfortunately, we don't live in a perfect world... and neither is rakugaki a perfect tool. What's happening here requires understanding the internals of rakugaki's graphics engine a bit, but bear with me---I'll try to keep it simple. As much as haku works on 32-bit real numbers, due to on-disk storage and memory considerations, rakugaki renders things in an 8-bit color space. Therefore, unlike haku, it can only represent color channels from 0 to 255, with no decimal point. There's Red 1 and Red 2, but no Red 1.5. ::: aside haku uses a standard representation of real numbers in the computer world, better known as IEEE 754 floating point. This standard has its quirks, such as `NaN`---a value that is *N*ot *a* *N*umber, in a standard representation for real numbers. Huh. What's even funnier is that `NaN` is not equal to anything, even itself. _Huh._ And what's _even_ funnier is that `NaN` infects anything it touches with itself. One plus `NaN` is `NaN`. It's like an error flag that propagates across your calculations, with no context as to what went wrong, and when. I gotta make the appearance of `NaN` a hard error in haku someday. ::: Now let's consider what blending colors does. Most commonly, colors are blended using _linear interpolation_---which is essentially, you draw a straight line segment between two colors in the RGB space, and take a point across that segment, at the alpha value---where an alpha of 0 means the starting point, and an alpha of 1 means the ending point. Mathematically, linear interpolation is defined using this formula: ```haku lerp = \a, b, t -> a + (b - a) * t ``` What we're doing when blending colors, is mixing between a _source_ color (the wall), and a _destination_ color (the brush) on each channel. Since the operations are the same across all four color channels, we'll simplify and only look at Red. But due to this reduced precision on the wall, we have to convert from a real number between 0 and 1, to an integer between 0 and 255 at _every rendering step_, with each splat of the brush rendered to the wall. Consider that we're drawing circles of opacity 0.01 every single time. Now let's look what happens when we try to blend each circle on top of a single pixel... ```haku lerp 0 255 0.01 = 0 + (255 - 0) * 0.01 = 255 * 0.01 = 2.55 ``` That's one circle. But remember that we have to convert that down to an integer between 0 to 255---rakugaki does this by removing the decimal part. ::: aside This is known as _truncation_. It is not the same as rounding! For negative results, it gives different results: `floor (-1.5)` would be `-2`, while `trunc (-1.5)` is `-1`. ::: So for the next step, we'll be interpolating from `2`, and not `2.55`... ```haku lerp 2 255 0.01 = 4.53 lerp 4 255 0.01 = 6.51 lerp 6 255 0.01 = 8.49 lerp 8 255 0.01 = 10.47 ... ``` I think you can see the pattern here. This continues until around 52, where the decimal point finally goes below zero, and now we're incrementing by one instead. ```haku ... lerp 52 255 0.01 = 54.03 lerp 54 255 0.01 = 56.01 lerp 56 255 0.01 = 57.99 -- !! lerp 57 255 0.01 = 58.98 ... ``` ...and at one point, we get to this: ```haku lerp 153 255 0.01 = 154.02 lerp 154 255 0.01 = 155.01 lerp 155 255 0.01 = 156 lerp 156 255 0.01 = 156.99 -- !! ``` Truncating 156.99 will get us to 156 again, which means we're stuck! This precision limitation is quite unfortunate, but I don't have a solution for it yet. Maybe one day. 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. 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. ## Reticles Having basic knowledge of functions and scribbles, you may be wondering: _what does that `withDotter` function do?_ It surely looks a bit magical, conjuring that `d` parameter from nowhere; and `d` contains everything we need to draw! Put simply, `withDotter` is what rakugaki calls a *reticle*. Reticles are pieces of data representing _interactions with the wall_. A reticle is usually composed of two parts: the reticle data, defining how the interaction is meant to be initiated, and a _continuation function_. When your brush gives rakugaki a reticle, it will take the reticle data, and let the user perform an interaction. Once the interaction is performed, it will give whatever user input it has gathered as an argument to the continuation function, which can return a scribble to draw on the wall, or another reticle. rakugaki will continue performing interactions until the brush gives back a scribble. `withDotter` is the simplest, most direct reticle for interacting with the wall. It allows you to paint freely, and gives the brush information on your current mouse position, previous mouse position, and _number of steps_ performed thus far. You've already seen the former two properties---those are `d To` and `d From`. The number of steps is available as `d Num`. This is an integer starting at 0, incremented by 1 with each execution of the brush, until you release your mouse cursor. This allows you to _animate_ your brushes over time! For example, this brush draws a rainbow. ```haku colorCurve = \n -> abs (cos n) 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 stroke 8 color (line (d From) (d To)) ``` Currently, `withDotter` is the only reticle available in rakugaki, and it cannot be chained due to its immediate nature: `withDotter` continues executing immediately after you move your mouse by the _tiniest_ bit, so it's unclear how to even continue after that! 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`? I mentioned before that you cannot have defs inside functions. What you _can_ have though, is `let`s, which define _variables_. 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. ```haku let 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. ::: 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. ```haku (\name -> then) value ``` This basic little trick with immediately applying a function stands the basis of a formal mathematical system called [_lambda calculus_](https://en.wikipedia.org/wiki/Lambda_calculus). It underpins a large number of functional programming languages, including haku, and more famously [Haskell](https://www.haskell.org/)! 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! Compare the above version of the rainbow brush to this version, where all the `let`s are written inline: ```haku colorCurve = \n -> abs (cos n) 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)) ``` 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. ## Have fun With that said, I hope you can have fun with rakugaki despite it being in its infancy! You may want to check out the [system library reference](/docs/system.html) now, to know what else you can do with the language---this little introduction barely even scratched the surface of what's possible!