Update on the Bagel language
brandons.me
brandons.me
1. It looks like 2. the comments 3. support markdown 1. Whoa.
> Do you have quotes?
```rust
fn foo() -> &'static str { "bar" } ```
# Headings
Maybe?
A link to [my website](https://fakeyou.com)
And maybe even an image

(Sorry, I just have to test this out. Your website is awesome.)
And of course
this is how you
do code fences.It’s almost all custom (you can see the source on my GitHub https://github.com/brundonsmith/website)
Most of the site is statically generated at startup using regular JS template strings, and then served as static files (cached in-memory) by Express. Posts are written in Markdown, and code blocks are processed into language-highlighted HTML using Prism.
The only dynamic bit is the HN comments, since I want those to stay fresh. My Node server queries the Algolia HN API to get all comments for the top HN link for the post (since there could be more than one), munges the data a bit, renders it as HTML, and then caches it for one minute. Some client-side JS just hits the endpoint and dumps the HTML into the page.
Any rich rendering inside comment bodies, like for code blocks and italics, actually comes directly from HN itself! The content I get has already been processed from HN’s special markup syntax into HTML, though of course the structural content around the comment bodies is custom.
Basically what I’ve found is that people often ask good questions under my posts, and the discussion/my responses end up being a valuable part of the post itself (clarification, expansion, etc), so I wanted that extra context to be available for people who don’t find my posts via HN. That’s also why I highlight my own comments in that view; I see them as a part of the blog post.
This site is my relaxing project that I tinker with occasionally, so sometimes I over-engineer parts of it a little bit for fun :)
The fact that I can't take something in a proc block, remove the side effects, and shove it into a func feels like effort for its own sake and makes it more difficult to use funcs when you want to maximize their use for the mental load of the caller.
Example:
func foo(a: number) =>
const b = a * 2,
const c = b + 17,
b * c
Between that and expressive conditionals, I think most ergonomic concerns about a single expression are covered (but I’m happy to deal with any others that come to light!)As for bifurcating the language: sorta, but there isn’t actually much syntax that’s different between the two contexts. Mostly the use of semicolons, and then the existence of loops in procedures. And all of it on both sides is designed to be familiar to the target audience.
> The fact that I can't take something in a proc block, remove the side effects, and shove it into a func
As a reminder, procs can’t return values at all, so it’s unlikely you’d ever really have a case to do this with a whole chunk of a proc. You might want to extract an expression out of a proc and into a function, but that should work pretty naturally as-is
Edit: and I think actually, the strict policy against return values for procs probably helps with the question of bifurcation in a way. Without it, some people might just prefer to construct their values procedurally. But with this limitation, the whole team has to be on the same page: if you want to re-use logic that derives a value, you have to use a func.
Though of course, recursion is always still available as a fallback/preference
1: Is there really no way to reuse procedural code in a pure setting? Say I have a procedure
proc inPlaceSort(a: mutable IntArray) { ... }
Is there any way to use this proc to define a func that returns a sorted copy? For example: func pureSort(a: IntArray): IntArray =>
mutable array_copy = copyOf(a),
sort(array_copy),
array_copy
Do I have to write two separate sorting algorithms? In Haskell I can implement both efficiently with a single implementation in the ST Monad.2: Can pure functions perform logging?
3: Can a procedure declare some parameters as immutable or as output-only?
- Immutable parameters: often you will want to perform an effect with some context which you don't want to mutate. Const parameters are heavily used in C++ and Rust.
- Output parameters: these are parameters that are initialized by the callee and not the caller. Most languages don't need this as they can return one or more values instead, but C# is a language that has this specific feature.
4: Why invent a new term "expressive conditional" when "conditional expression" already exists? :^)
2. I’m planning to make an exception here for debugging purposes; I’m thinking a function or operator that logs a value and then evaluates to it. Not strictly pure, but close enough from a maintainability standpoint.
3. Yes, any type can be labeled “const”, which is recursive. If a proc has a const argument, an otherwise-mutable object can be passed to it and it will just be treated as const within that proc. However, a const object cannot be passed to a non-const parameter (or variable, etc), otherwise some other code could then mutate it. Assignable (output) parameters will not be supported, but contents of parameters can be mutated by procs. I specifically wanted to discourage people from using procs to generate values; they should only say what to do with the values.
4. I may not have been fully versed on the terminology :)
I haven't used Svelte firsthand, but I definitely researched it as part of my process for Bagel.
As far as influence goes: Svelte definitely turned me on to the idea that you could skip a lot of the metaprogramming and overhead that MobX has to do, by injecting observe/invalidate calls at compile time. Both the bundle size and runtime memory usage should be dramatically lighter-weight as a result.
With that said, Bagel's reactivity system is different from Svelte's in a couple of key ways:
1. From what I remember, Svelte can only react to assignments to variables, not deep mutations. If you do a deep mutation, the docs instruct you to re-assign the top level object so that it triggers an update. Bagel on the other hand - like MobX - keys off of object references + property names; each pair can be subscribed to and published individually, regardless of where the object lives. Assignments to raw variables are actually a special case in Bagel; the compiler injects a hidden sentinel object to make those assignments reactive.
2. Unlike Svelte, Bagel doesn't aim to be a DSL for UIs; it's trying to be a general-purpose language. This means it can't rely on, for example, a component lifecycle for standup/teardown of reactions. This forced some rethinks, as discussed in the article, but I think it worked out in the end. The generality also means Bagel can't easily replicate Svelte's "DOM-surgery" approach to reactive UIs (which is really cool, and probably going to change the industry); so I'm sticking with a VDOM model right now. With that said, Bagel is general enough that somebody might be able to add that functionality later in the form of a library!
(I feel the need to post a disclaimer, that the current implementation of Bagel’s reactivity system is very crude end inefficient, and probably incomplete. I wanted to prove out the idea and then move on for the time being; but it will be much more fleshed out before release)
As for why I don't use Svelte personally- partly I'm uncomfortable with the lack of deep reactivity; I think it would be really easy to forget to re-assign something. The other reason is that (last I checked) it seemed like static types (via TypeScript) are somewhat of a second-class citizen. My understanding is that Svelte shipped without support for TypeScript, and it got added later as people asked for it, but it's still only "mostly" supported because of Svelte’s DSL nature and some parts don’t get fully type-checked. I'm a big advocate of static types, so this is something that bothers me.
With all that said, I have a lot of respect for the Svelte folks and I wish y'all success!
A sentinel value could work, or even just passing all the args together as a tuple. Haskell and js frequently do the latter.
Sad though, it's a great feature.
Edit: that would be a pretty good nickname for the “spread” operator, actually…