Compare commits

..

No commits in common. "2f92beae6870a560c966b9997d44cdf5bdc24f1e" and "64c6e175a8a11e3f0953524970d4f221518747b6" have entirely different histories.

7 changed files with 50 additions and 318 deletions

View file

@ -1,8 +1,8 @@
---
title: An Introduction to Coroutines
title: An Introduction to Coroutines
description: "A language-agnostic introduction to the concept of Coroutines"
published: true
tags: coroutines parallelism
tags: beginner coroutines parallelism
date: 2021-11-20
---
@ -21,75 +21,39 @@ In describing coroutines, the introductory sentence of the [wikipedia article](h
Put even more simply: A coroutine is *like a function* that can pause itself.
If we think of a normal function, the way it works is that we **call** the function, the function **executes** and at some point, it **returns** back to where it was called.
In a similar way, a very simple coroutine will do the same thing: We will **create** the coroutine, it will **execute** and at some point it will end and (implicitly) **yield** back to the calling code.
Here is a simple example of how this looks in practice.
First we define a new coroutine that yields three times and finally prints "Done"
```js
my_coro = coroutine(=> {
yield(1)
yield(2)
yield(3)
print("Done")
})
```
Now we can resume the coroutine up to 4 times, and each time it will resume from the last `yield` and stop at the next one, or when it reaches the end of the subroutine.
```
first_yield = resume(coro)
// At this point the coroutine is paused on its first line
print(first_yield) // prints 1
second_yield = resume(coro)
// At this point the coroutine is paused on its second line
print(second_yield) // prints 2
third_yield = resume(coro)
// At this point the coroutine is paused on its third line
print(third_yield) // prints 3
resume(coro) // prints "Done"
// At this point the coroutine has completed its execution.
```
In a similar way, a very simple coroutine will do the same thing: We will **create** the coroutine, it will **execute** and at some point it will end and (implicitly) **yield** back to the calling code.
The big difference is: A coroutine can **yield** more than once, and will be paused in between. And that is really all there is to them, from a technical level. A simple example of this would look like this.
## Classifying Coroutines
The 2004 paper [Revisiting Coroutines](http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf) classifies coroutines in two important ways: *Symmetric vs. Asymmetric* and *Stackful vs. Stackless*. The paper also distinguishes on whether coroutines are handled as values by the language, but that distinction is less important to understanding how they fundamentally work.
### Control Transfer Mechanism
The 2004 paper [Revisiting Coroutines](http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf) classifies coroutines in two important ways: *Symmetric vs. Asymmetric* and *Stackful vs. Stackless*. The paper also distinguishes on whether coroutines are handled as values by the language, but that distinction is less important to understanding how they fundamentally work.
### Control Transfer Mechanism
The way coroutines transfer control can happen in two ways.
Asymmetric coroutines are more similar to how functions work. When a coroutine *A* resumes a coroutine *B*, *B* will at some point yield back to *A*, just like how any function will eventually return to its caller. We can think of these coroutines as organised in a stack, just like how functions are, but this is not the same as being *stackful*.
An important implication of this type of control transfer is that once a coroutine hands over control to another by resuming it, it can only ever be handed back control from this coroutine. In other words, it cannot be *resumed* from the outside, as it is already "running", only *yielded* to.
An important implication of this type of control transfer is that once a coroutine hands over control to another by resuming it, it can only ever be handed back control from this coroutine. In other words, it cannot be *resumed* from the outside, only *yielded* to.
To illustrate this: "When you lend a pencil to Bob, you know you will eventually get the pencil back from Bob and nobody else."
This will be represented in pseudocode by the functions `resume` ("hand control down") and `yield` ("return control back up")
In the previous section, the example represented this kind of coroutine. It handed over control three times using the `yield` function without specifying where to hand control back to. It simply went back to wherever the coroutine was resumed from.
---
Symmetric coroutines work a bit differently. Coroutines can freely transfer control to any other coroutine, instead of just "up and down".
Unlike asymmetric coroutines, this one-way control transfer means a coroutine can hand over control to another and be handed back control by a completely different one.
Continuing the pencil analogy: "When you lend a pencil to Bob, you may later get it back from Steve, Larry, or never get it back at all."
Continuing the pencil analogy: "When you lend a pencil to Bob, you may later get it back from Steve, Larry, or never get it back at all."
In pseudocode, this will be represented by the `transfer` function.
---
The main advantage of asymmetric coroutines is that they offer more structure. Symmetric coroutines let the user freely jump back and forth between coroutines, in a similar way to `goto` statements, which grants a much higher degree of freedom but can also make code hard to follow.
The main advantage of asymmetric coroutines is that they offer more structure. Symmetric coroutines let the user freely jump back and forth between coroutines, in a similar way to `goto` statements, which can make core hard to follow.
### Stackfulness
Another important way to categorize coroutines is whether every coroutine has its own stack. This distinction is much harder to explain in theory, but will become clear in examples later on.
Stackless coroutines, as the name implies, don't have their own call stack. What this means in practice is that the program has no way of tracking their call stack once they yield control, so this is only possible from the function on the bottom of the stack.
@ -102,9 +66,9 @@ A complete explanation of why this is and how it works could easily be its own a
Many programming languages actually have *stackless* *asymmetric* coroutines; JavaScript, for example, calls them *generators*.
Some languages even have a mix of both: Ruby *fibers* can both use `resume`/`yield` semantics, but they can also freely transfer control freely with the `transfer` method.
Some languages even have a mix of both: Ruby *fibers* can both use `resume`/`yield` semantics, but they can also freely transfer control freely with the `transfer` method.
Windows even provides an OS-level API for coroutines: It calls them Fibers, and they are *stackful* and *symmetric*. Linux does not provide any coroutine API yet.
Windows even provides an OS-level API for coroutines: It calls them Fibers, and they are *stackful* and *symmetric*. Linux does not provide any coroutine API yet.
## Why are they useful?
On an abstract level, the strength of coroutines is to manage state. Since they remember where they left off for the next time they're resumed, they can use control-flow to save state that would otherwise have to be stored in variables.
@ -169,7 +133,7 @@ object.move_right = distance ⇒ {
This could be refactored into a single `move(axis, distance)` function, of course; but the additional code to figure out the direction would clutter the code a bit too much for this example.
The important thing here is, that the top-level function of the coroutine never `yield`s; instead, it calls a `move_*` function that takes care of yielding itself. This is where **stackfulnes** comes into play again: Only **stackful** coroutines can do this. In languages with stackless coroutines, like javascript, code like this would likely be rejected by the compiler.
The important thing here is, that the top-level function of the coroutine never `yield`s; instead, it calls a `move_*` function that takes care of yielding itself. This is where **stackfulnes** comes into play again: Only **stackful** coroutines can do this. In languages with stackless coroutines, like javascript, code like this would likely be rejected by the compiler.
Put very simply: the reason for this is that when `move_right` yields, it needs to remember where it needs to return to after it resumes. This information is what's on the stack, so a coroutine without its own stack cannot remember from nested functions.
@ -178,8 +142,8 @@ Another application of coroutines is handling the complexity if asynchronous cod
But how exactly can coroutines help with this? Simple: by yielding to an event-loop, which will resume them once a certain event happens. While in practice this is a bit more complicated, the core idea is this:
* All code runs inside coroutines (In some languages this is always the case, in others it would be up to a framework to run any user code in a coroutine)
* Functions that need to await asynchronous tasks simply start an I/O operation, then yield some object describing when to resume them back to the top level coroutine
* All code runs inside coroutines (In some languages this is always the case, in others the framework would have to wrap the user code manually)
* Functions that need to await asynchronous tasks yield from the current coroutine
* When a coroutine yields, a scheduler will decide what coroutine to resume next, or simply sleep until any new "event" is available.
This should sound very familiar to anybody who has worked with `async`/`await` before. It is a very similar concept. Then only difference is that functions are always synchronous, and all functions are implicitly awaited.

View file

@ -1,9 +1,7 @@
---
title: Why I prefer Lua to Ruby
description: "A short post describing the main reasons why after years of using
both languages, I still prefer Lua to Ruby for most general situations."
published: true
tags: Rant, Lua, Ruby
tags: Lua, Ruby
date: 2019-04-01
---
@ -13,7 +11,7 @@ Today I'd like to point out some reasons that have shaped my opinion on the two
I will risk sounding like a hater, but I'd like to point out that I don't particularly dislike Ruby. There's many positive things about it, but since I do prefer Lua, I just chose to point out some of its problems today.
## Speed
# Speed
It'd be easy to just say "Lua fast, Lua good" and be done with it. In reality though, things aren't always that easy.
@ -39,6 +37,8 @@ sys 0m0.001s
This only makes a difference when you're calling the executable many times in a row, but the difference does add up over time.
----------------------
### Functions vs. Methods
The next thing to consider is that, in Lua, functions are stored in variables, and are therefore easy to look up.
@ -56,6 +56,8 @@ Even the main object every ruby script runs in inherits from 3 ancestors.
It should be noted that Lua isn't immune to this problem. When writing object-oriented code, this also ends up happening. The difference is that in Lua the programmer must do this explicitly, while in Ruby it happens all the time.
----------------------
### Strings / Symbols
~~This may surprise some people, but Lua actually has a huge disadvantage to Ruby in terms of speed: It has no Strings in the way Ruby has them. All strings in Lua are automatically interned. Ruby has interned strings as well; it calls them *Symbols*.~~
@ -72,6 +74,8 @@ However, the downside that Lua strings are immutable remains, and modifying a st
`</edit>`
----------------------
### Vararg functions
If I was asked which of the two languages had the powerful implementation of variadic functions, I'd 100% say Ruby. You can mix and match many kinds of syntactic constructs that capture additional arguments into both arrays and hashes.
@ -101,6 +105,8 @@ Here, there's no array involved. The function can just leave bar on the stack, c
This means writing functions like this is way more viable even when your code needs to run as fast as possible.
----------------------
### Mentality
One difference that probably makes a way larger difference than most people would assume is the difference in mentality between the two communities.
@ -109,13 +115,17 @@ When asked why Ruby is so great, the general response from its community will be
My experience with the Lua community has been vastly different. A simple google search brings up way more relevant and detailed information on how to improve performance in Lua than in Ruby, even though the latter has a much larger community that also seems to write way more about it.
----------------------
### LuaJIT
Lua is already very fast on its own. LuaJIT though, that's a completely different level. There have been examples where JITed Lua code can even run faster than equivalent C code, because the compiler sometimes has more awareness of the code it's optimizing (After all, it keeps track of the code as it's being executed)
Ruby has made a huge step in the right direction with its own JIT Compiler, but that's still nowhere near the performance improvements of LuaJIT when compared to PUC Lua, the "reference" Lua implementation.
## Simplicity
-------------------------------------
# Simplicity
Leaving performance aside now, there's another reason why I consider simplicity a very positive feature of Lua: It's not only easy to learn, but also easy to master.
@ -139,6 +149,8 @@ local someclass = require 'someclass'
local foo = someclass.new()
```
----------------------
### Side effects
Ruby tries to be as comfortable as possible for the developer. I often find myself wishing it didn't
@ -161,6 +173,8 @@ Even worse, maybe some developer thought if we wanted to know about the last mis
This kind of thing happens often in Ruby. The lines between what is a value and what is code that gets executed are blurred.
----------------------
### (in)Consistencies
Try the following code in `irb`:

View file

@ -4,18 +4,17 @@ published: true
description: A long and poorly structured rant about a series of harmful phenomena I've observed in the world of programming.
tags: rant, programming, community
date: 2020-11-25
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/5f05h9bj7sda2erlf2uc.JPG
---
## Preamble
# Preamble
Over the last decade or so, I've had a peek into quite a few developer communities. I do most of my private programming in Lua, and interact with that community almost daily. At work I use Ruby, and often get a good glimpse of that community as well. Through Hacker News, Reddit and a bunch of other sites I get a good impression of many different "general" programming communities. I am only human, so my opinions will of course be biased, but for what it's worth, I do believe that I have at least some degree of objective perspective on this topic.
## The Problem
# The Problem
Lately, I've noticed a bit of a trend within the world of software development. Put bluntly, it seems to me like the field of programming is slowly but surely drifting down the hillside, spearheaded but not exclusively caused by the web community in particular.
## The Symptomatology
# The Symptomatology
What do I mean by this though? Well, starting with the a parallel, but mostly positive process: Programming is becoming more and more inclusive and easy to get into. Elitism is slowly becoming more of a meme than an honest feeling of superiority, and more and more people are picking up programming or closely related skills.
@ -35,7 +34,7 @@ Why does this matter? Consider the public image of programming. Currently, most
So from that perspective, constantly reiterating the idea that programmers are just code-monkeys that only google things is, to put it bluntly, strategically stupid.
## The Mythology
# The Mythology
The idea that programmers just copy together all of their code is far from the only harmful idea out there though. Parallel to it, a much more harmful myth is that "everyone can code [on the same level]". Now, just as with drawing, music and many others, I do believe that everyone, without exceptions, is capable of learning the basics of programming.
@ -45,7 +44,7 @@ So how does that persistent myth do any real harm? In the same way the Google an
On the other side, it sometimes seems that employers still haven't given up on the mythical plug-n-play developer that can be employed today and start being 100% effective tomorrow. It used to be that OOP promised developers who could be interchanged without any effort, and instead of just disappearing, this supersticion seems to just have shifted to the ridiculous belief that a developer can specialise in a specific tech stack to the degree where they can just be sat down before a project and they'll instantly know how to make the product better.
## The End Game
# The End Game
Is all of this coincidence? Well, it probably is. I don't believe there's any mastermind about us programmers shooting our own leg and devaluing our own skills. However, there is someone who profits from it, and certainly has good enough reasons to help the process along occasionally.
@ -61,7 +60,7 @@ The strategy seems obvious: gasslighting us all into believing we're of very low
While everybody can estimate how much skill and effort goes into, say, building furniture, it is very hard if not impossible to the uninitiated to get a feeling for how difficult it is to build a software product. It seems almost like black magic to the outsider, and this is another thing that people often joke about (sadly, the punchline is often that ultimately, we're not doing anything at all difficult).
## The Remedy
# The Remedy
It is hard to say what could be done to push programming back up the hill. There seems to be a strong culture of self-deprecation (probably adopted from broader nerd-culture), and a tendency to unironically under-value ones own skills in this community. The amount of posts I find about impostor syndrome every week on its own already indicates that we have a serious problem that needs to be addressed.
@ -71,6 +70,6 @@ As for the focus on finding employment, I really think that needs to be toned do
And please, don't sell yourselves under value. Programming is hard. Everyone can learn it, but few can master it. We're not just code-monkeys copying from google. And if you're learning: Try having fun and don't worry too much about maximising your employability.
## In Conclusion
# In Conclusion
It's not like I think the world will end. Programming won't stop being my hobby, nor do I fear I some day won't find a job (at least not before someone develops an AI that does all the programming for us and understands human language). I want the world of programming to stay this quirky place, where people build cool stuff, share what they've built and discuss it with others. Ultimately I hope we can all have fun programming 🧡

View file

@ -6,23 +6,23 @@ tags: indentation, tabs, spaces
date: 2021-02-15
---
## The Debate
# The Debate
This is probably one of the longest ongoing bikeshedding debates in the programming community: Should we indent our code with **Tabs** or with **Spaces**?
In this post, I will do my best to explain why **tabs** are the right choice, not only in my personal opinion, but objectively.
## Indentation
# Indentation
First of all, I want to define the scope of my argumentation: I am referring to **indentation**, not **alignment**.
The former depends on semantics, the latter on word lengths. For obvious reasons, using tabs for alignment is not possible; whether alignment should be a thing at all (hint: it shouldn't) or how it should be achieved is a different debate and irrelevant for this post.
## Semantics
# Semantics
The most puritan argument for tabs is probably the semantic information they add to the code. I have never seen a real-world example where this matters, but as programmers we often like to obsess over using the right "thing", be it a HTML element or an ASCII character.
## Consistency
# Consistency
The main argument I've heard defending spaces is that code looks "consistent" everywhere. Whether you post your code on Stack-Overflow, GitHub Gists, or on your blog; it will always be indented by the same width.
@ -30,7 +30,7 @@ Code is not visual art. The indentation width doesn't alter the codes meaning in
**There is no reason whatsoever why someone else reading my code should experience it with the same indentation width that I wrote it in.**
## Customizability
# Customizability
I personally prefer a tab-width of three spaces. Two is just barely too short to follow through deeper nested blocks of code. Four is one more space-width than I need.
@ -40,7 +40,7 @@ The answer is, of course, you shouldn't. You should be able to read *my* code th
**Everyone should be able to read code with their own preferred settings.**
## Accessibility
# Accessibility
So far I've looked at customizability as a convenience feature. I *like* 3-space indentation more, so I *want* to read code that way.
@ -48,7 +48,7 @@ But for some people it goes beyond just preference.
I've seen posts and comments of quite a few developers with poor eyesight. For some, 2 spaces is just not enough indentation, making it unnecessarily hard to read code, others might need to use very large font sizes, and prefer shorter indentations to save screen space.
## Consistency (again, but differently)
# Consistency (again, but differently)
This is by far the most ridiculous reason, or group of reasons people make to argue for spaces:
@ -58,6 +58,6 @@ Needless to say, this argument works exactly the same both ways, and if anything
Regardless of preference, in the age of linting tools and CI pipelines, this is just not an issue any more. We can automate the process of checking indentation, or even have it fixed automatically.
## Conclusion
# Conclusion
There is not a single good reason to prefer spaces over tabs. The whole space-indentation mythology is nothing but ridiculous non sequiturs and false claims.

View file

@ -1,41 +0,0 @@
---
title: Upcoming CSS features in 2023
published: false
description: "A short explanation of the upcoming CSS features I'm the most
excited about in early 2023, and why I can't wait for them to be official."
tags: css,scope,components,vanilla
date: 2023-02-27
---
## CSS Scoping
> The `@scope` block at-rule allows authors to scope style rules in CSS, with
> the application of weak scoping proximity between the scoping root and the
> subject of each style rule.
>
> <cite>CSS Cascading and Inheritance Level 6 (W3C First Public Working Draft, 21 December 2021)</cite>
## Relative Colors
> Within a relative color syntax `color()` function using <custom-params>, the
> number and name of the allowed channel keywords are:
>
> <cite>CSS Color Module Level 5 (W3C Working Draft, 28 June 2022)</cite>
## Contrast-Color
> The `contrast-color()` functional notation identifies a sufficiently
> contrasting color against a specified background or foreground color without
> requiring manual computation.
>
> <cite>CSS Color Module Level 6 (Editors Draft, 15 February 2023)</cite>
## CSS Nesting
> Style rules can be nested inside of other styles rules. These nested style
> rules act exactly like ordinary style rules—associating properties with
> elements via selectors—but they "inherit" their parent rules selector
> context, allowing them to further build on the parents selector without
> having to repeat it, possibly multiple times.
>
> <cite>CSS Nesting Module (W3C Working Draft, 14 February 2023)</cite>

View file

@ -1,5 +1,5 @@
---
title: "Type-Writer Component: Magic and Asynchronicity"
title: Building an HTML Type-Writer
description: "A detailed description of the development process of an HTML TypeWriter element in plain JavaScript using the custom-element API, meta-programming and some recursion magic."
published: true
tags: type-writer, html, javascript, webdev, async, metaprogramming
@ -277,3 +277,5 @@ I hope this article was of some use, specially in illustrating how meta-programm
I am aware that this example doesn't really target only one skill-level, as something like recursively traversing a tree is a lot more "basic" than dynamic property definitions and other meta-programming, but I hope that everyone can find at least one or two things they find helpful.
And last but not least, [here's the actual code](https://github.com/DarkWiiPlayer/components/blob/master/TypeWriter.js), but keep in mind that I changed and ommitted a few things to make the code easier to follow in the article.
What are your thoughts? Would you improve anything about this aproach? Have you ever found yourself using similar tools to solve a problem? Let me know in the comments and happy discussing! 💜

View file

@ -1,206 +0,0 @@
---
title: Making the case for Skooma
description: In this short post I present a simple personal library I wrote for myself to handle HTML generation in JavaScript, compare it to many of its alternatives, and evaluate whether it was worth the time investment of not just picking an existing library.
published: true
tags: html, javascript, templating, metaprogramming, webdev
date: 2023-08-06
---
## Introduction
Skooma.js is a small library I built for myself to solve the problem of generating dynamic HTML from vanilla JavaScript. It's an adaptation of a Lua library of the same name to JS, with some additional quality of life improvements that only work because it runs in the browser.
The point of this post is not to convince anyone to use it. The API is relatively stable and it seems relatively bug-free by now, but this is still primarily intended as my personal helper library. Instead I want to make an argument for why I think it was a good idea to build it.
## History
Skooma, as mentioned above, started out as a Lua library to generate HTML on the server side. The core concept is simple: for every HTML (or XML) tag, there is a function that takes the tags contents and returns some representation of the tag. While the library allows users to mutate the returned elements (represented as a reeeeeally simple DOM structure), the library itself is free of side-effects (with the exception of certain helpers that are explicitly about side effects), so it works well with a functional approach.
You can map an array of strings with the `span` function to turn it into an array of `span` elements containing the strings as their text. Complex structures ("components") can easily be composed as new functions.
The primary motivation for this library was a general dissatisfaction with existing ways of writing HTML. Plain HTML and most templating languages are cumbersome to write. And while modern editors make the experience a lot less painful, with features like auto-closing tags, it still seems a bit backwards to need such heavy tooling to just write it.
Some of the more DLS-like templating languages like HAML get a lot closer to what I want, but I am really not a fan of having a separate DSL that is not quite its host language, not quite HTML and also not quite its own language.
A better approach, in my mind, was the one found in the [lapis](http://leafo.net/lapis/) framework, where html is generated from Lua functions (usually moonscript compiled to Lua, which makes for cleaner code) that can be nested. I also wrote my own iteration of this concept in the form of MoonHTML, but eventually abandoned the project because emitting the HTML as a side-effect instead of returning it came with a variety of scalability problems that made it easier for smaller templates but more complex in bigger projects.
The result was eventually Skooma (named after a fictional drug in the Elder Scrolls universe, made from a substance called *moon sugar*), which I still use to this day whenever I have the need to generate some HTML programmatically or just don't feel like typing out the actual HTML.
Given the many similarities between Lua and JavaScript, it was only a matter of time for me to decide to port the concept from one language to another, and the fact that browsers already have a DOM API means that whole part of the library can be removed and the result is still more powerful than working with my custom mini DOM.
## Features
Skooma is best explained by example, as a big part of the point is the (relatively) clean-looking code that looks *somewhat* like a purpose-built DSL.
```js
import {html} from '/skooma.js'
const user = ({name, email, picture}) =>
html.div({class: 'user'},
html.img({src: picture}),
html.h2(name),
html.a(email, {href: `mailto:${email}`})
)
fetch_users_from_somewhere()
.map(user)
.each(document.body.append)
```
In this example, I define a simple `user` component that takes an object with some attributes and generates a DOM structure to represent it. The HTML object is a proxy that generates functions for generating DOM nodes as writing `html.div(content)` is a lot nicer than `html.tag("div", content)`. `Proxy` really is a vastly underrated JavaScript feature.
The outermost `div` tag is given three other tags as its children, and an object representing its HTML attributes. This API is very flexible; one can pass several child elements and objects in whatever order and even nest them in arrays (which can then be re-used).
### Event Handlers
Since passing functions into HTML attributes makes no sense, this case is used for setting event handlers instead:
```js
html.button("Click me!", {
click: event => alert("Button has been clicked!")
})
```
This internally uses `addEventListener` instead of setting an `onclick` attribute, so this even lets one add several handlers of the same type, albeit in separate objects.
### Initialisers
Just like with attributes, putting a function in the child-list of a DOM element makes no sense, so this case is used to simply pass arbitrary initialisers to the element. These get called as soon as they're found instead of deferred, so any arguments that follow will not yet be applied. I have no strong opinion on whether deferring them would make more sense and might implement this if I ever find a good reason to prefer it.
```js
const register = element => { my_element_registry.add(element) }
// ^ pretend this gets used somewhere else to do something useful
html.button("Click me?", register)
```
### Dataset
Data-attributes can instead be set by passing a special `dataset` object key to an element constructor:
```js
const form_children = [/* ... */]
html.form({
'data-id': '0001', // this is ugly
dataset: { id: '0001' }, // this is nicer
}, form_children)
// Excessively hacky and ugly:
html.form(
Object.fromEntries(Object.entries(user).map(
([key, value]) => ["data-"+key, value])
),
form_children
)
// This is how things should be:
html.form({dataset: user}, form_children)
```
### Shadow-Root
Likewise, the key `shadowRoot` is also special, in that its value is added to the new object's shadow root, which is created if it doesn't exist yet. This follows the same logic as the function's arguments, so it can be a DOM node, a string, or a (possibly nested) array of child elements.
```js
html.div({
shadowRoot: [
html.h2("Greetings from the shadow DOM!"),
html.slot(),
html.span("It's very shadowy in here...")
]
}, html.i("Wait, where am I?!"))
```
### Styling
Similar to `dataset`, the `style` attribute can be used to pass an object and have its values assigned to the DOM node's `style` attribute.
```js
html.span({
style: {
textDecoration: 'underline',
// gets transformed to kebab-case
}
})
```
### Custom Elements
Custom elements, which have hyphens in their names, don't have to be created using square braces (although you can, if you hate yourself) `html['my-component']("inner text")`; instead, camelCase tag names are converted to kebab-case just like style properties and html attributes, so you can just write `html.myComponent("inner text")` instead.
```js
html.typeWriter({
customProperty: "I have a property!",
}, [
html.span("Greetings, I am a custom element"),
])
```
## As a Learning Experience
All in all, this was a really fun project to implement. `Proxy` objects are really nice, and porting the code from Lua, which has a completely different way of doing a very similar thing, was ultimately still really easy. And even though I only used a small part of the `Proxy` API, I still used the chance to read up on some of the other possibilities it offers. `Proxy` is cool!
## Comparing to Alternatives
### Interpolation++
Comparing skooma.js to anything from `String ${'templates'}` to traditional PHP, where you still write HTML but can interpolate content into your output and sometimes even insert blocks of logic into it, skooma does as good a job as all the alternatives below at getting rid of my primary problem: HTMLs annoying syntax.
### HAML & Co.
These templating "languages" honestly aren't bad. I am perfectly happy writing HAML templates whenever I'm having to work with rails, and any of its alternatives in other languages would work as well.
My main problems with these are the context-switching between languages, and the fact that it's not nearly as easy to refactor by extracting common structures into sub-components, as you can't just draw them out into a function and use it later on.
### VanJS
Had I found this library before writing Skooma, I probably would have just used it instead. It does basically the same thing, albeit with less convenience features, and will most likely still use this for work projects, as it has the benefit of being a "proper" framework (i.e. I didn't write it myself and it has a fancy website), so it will simply seem more legitimate to coworkers. Gotta love workplace politics.
I will say though, that I am not at all a fan of how it encourages importing all the tag functions into the current scope. That just screams scalability nightmare, and in any bigger project this will inevitably lead to a) lots of unused tag functions still being declared and b) constant "why isn't it wor— oh I haven't imported `ul` yet"
But that's just a style choice and the library doesn't force you to do things that way.
### Skooma (Lua)
This is, to me, the gold standard of syntax. Lua has some small advantages in its syntax that make it easier to make code look nice:
1. Ommitting braces when calling a function with a table literal as its only argument
2. Tables acting both as arrays and maps
3. Semicolons being allowed instead of commas (I hate commas for multi-line things)
4. Overriding `_ENV` and loading code with custom environments
So the `user` component from my first example could instead be written like this:
```lua
function user(u)
return div{
class = "user";
img { src = u.picture };
h2 { name };
a {
email;
href: "mailto:" .. u.email
}
}
end
```
And if I want to instead write it in moonscript or yuescript, a language (and a dialect of it) that compiles to Lua, I could even write my component like this:
```moon
user ==>
div class: "user"
* img src: @picture
* h2 name
* a email, href: "mailto:#{@email}"
```
which is starting to look almost like an actual DSL, except it's not, and I can put as much "real programming logic" like loops, function calls, etc. right in my HTML code and the syntax is the exact same between logic and template.
Admittedly, I could *probably* achieve the same in JS if I used something like CoffeeScript.
## Conclusions
1. Writing this library was definitely worth it. It was fun, I got to practise using `Proxy` and the result is definitely quite usable. I would absolutely do this again and encourage anyone to try projects like this one even if just for the fun aspect alone.
2. I already am and will continue to use skooma.js for my personal projects. Once I got used to it, it just feels weird to imagine using things like JSX or even assembling DOM nodes by hand using browser APIs.
3. I can't really be bothered to lobby people to use my cute little project and turn it into a "real thing", but if you're interested, or maybe even use VanJS and want to see how it compares, by all means, use Skooma. It's as production-ready as a single dev can make it, and due to its simplicity, bugs happen rarely and can usually be fixed within half an hour. Documentation is available at [darkwiiplayer.github.io/js/skooma.html](https://darkwiiplayer.github.io/js/skooma.html)