Design decision for Observable Assignments

Why was the sole equal signs chosen for the assignment operator for Observable variables? Why not use something like…

  • The Walrus Assignment ( variable := ... or variable := { ... } )
  • A valid Javascript grammar in a different construct ( variable : ... or variable : { ... } )
  • Or nearly anything else.

By using a Javascript construct that makes grammatical sense to Javascript in that location for Observable assignments means that developers have to “hack” around that design decision.

This valid Javascript ( variable = { ... } ) would need to be ( variable = ({...}) ) to be a valid Observable assignment. Note the added parentheses.

Honestly, this seems like a terrible design decision. The non-ability to just copy-paste all valid javascript after the equals sign because it might be interpreted by Observable as something else only creates more friction and there doesn’t seem to be any meaningful reason for it.

I think the design decision makes good sense, as it evokes the idea of an Immediately Invoked Function. In Javascript, we often see:

stuff = (function() {
  // generate stuff
  return 'stuff';
})()

I understand the reason to want to use constructs already in the language. Creating something purely Javascript. Perfect! But that stuff variable isn’t pure javascript. It’s a special Observable variable. It is different and that is what’s important.

stuff := (function() {
 // generate stuff
 return 'stuff';
})()
person := {
    firstName : "John",
    lastName : "Doe", 
    age : 50, 
    eyeColor : "blue" 
};

This doesn’t harm the readability of the code at all. Anything after the := would work as-is because it is pure javascript and doesn’t need anything that someone could call as a workaround.

In fact, Observable specifically state that Named Cells are fundamentally different from Javascript assignments. It is that difference that allows out-of-place running of code. It is an Observable Declaration; not a Javascript Assignment.

This is something they have to mention because it is fundamentally confusing to newcomers. But by using a construct not in Javascript would instantly make that understandable because it isn’t Javascript, which it also enables new capabilities.

I’m really not quite sure what you’re saying here. The stuff variable that I defined above is perfectly legitimate Javascript; the code behaves the same whether I input it into node, a browser console, or Observable.

My impression is (though I could be mistaken) that all syntactically correct Observable input is also syntactically correct Javascript. For example,

o = ({a:1,b:2})

produces the expected Javascript object whether it’s typed into the browser console or into an Observable cell.

Your suggestion to use

o := {a:1,b:2}

does not parse as Javascript.


As I understand it, named Observable cells should be thought of as hoisted functions declarations. Presumably, that’s exactly what they are once called by the runtime. In that context, there’s no parser inconsistency at all since an expression like

() => {a:1,b:2}

throws the same syntax error in the web console that’s thrown by the Observable runtime. On the other hand,

() => ({a:1,b:2})

works as expected.

2 Likes

You are looking at the wrong aspects. At least not the part that I am talking about. I agree with everything you said, but it’s not what I am talking about.

Yes, you are correct. And that is the fundamental problem here. What I am saying is that there is a difference between Observable and Javascript.
Before that equals sign is NOT javascript. It’s Observable. It has different meanings that goes beyond Javascript.
It is this exact difference in which we have to modify some javascript code to make it work in Observable.
You even understand this exact difference. The equals size signifies a hoisted function. This deserves repeating. The equals size signifies a hoisted function.

Equal signs in Javascript don’t mean hoisted functions at all. This is the issue. But in Observable, the equal size can. Therefore there should be a special operation for them.
:= was just an idea. Don’t like that specific symbol, then chose another one. It could be => or literally anything else but not a plain old =

Again, in Javascript = does not mean hoisted function. It never has and never will mean that.

Just like flattening a globe creates some issues in some manner. Converting between Observable and Javascript will do the same. In Observable, there are issues importing Javascript, but exporting seems like there are fewer issues.
I believe that is the wrong way. Importing Javascript should be a 1st-class citizen operation! It isn’t. Exporting is 1st-class. Importing Javascript into Observable should work near 100% of the time with no need for modification at all (That is not the case today).
With an even better UI, Observable can be Import-1st-class and allow for very easy exporting.

But the key is that Importing should work as-is with no need for modifications.

Actually this produces an issue. An undefined variable o. All valid Javascipt variable start with let or const

document.querySelector('#my-fun-element').innerText = 'What a fun element!'

…is also valid javascript that fails in Observable. The issue is because the = is serving multiple purposes. Again, variable assignment and hoisted function. They are mutually exclusive. Having some different operator will help explain why this fails.

I worry my response here will be unwelcome (because I don’t really know much about JS in general), and I hope that it’s taken with some empathy.

In this particular example you gave, it isn’t the = that makes the statement fail. Rather, it’s the lack of a div into which to render the element. The statement is valid and will work:

And here’s the notebook:

The thing is that it won’t work when you load a notebook without running the cell. I ran into this a few times and had trouble with it. From recollection, Tom showed me a way around it, but searching the forum I can’t find that particular thread. However, if after loading the notebook you run the HTML cell, it will work (as indicated by the image here).

The part of this conversation that I am not clear about is this:

I was under the impression that an Observable cell is just a container for Javascript, which is precisely why one has to used tagged template literals to paste in HTML, markdown, or otherwise.

Have you come up on other valid JS that can’t just be pasted directly into a cell?

1 Like

Exactly. There’s no way to expect the cell defining the DIV to have run, given that things don’t run top to bottom.

I would think the way to do it would be to name the cell defining the DIV, as in:

a_fun_cell = html`<div id="my-fun-element">`

and to place the document.querySelector command into another cell that depends on the name, as in:

{
  a_fun_cell;
  document.querySelector('#my-fun-element').innerText = 'What a fun element!';
}

You are correct about the first part. That was an oversight on my part. Mostly due to being new. It does work, but doesn’t really fit the Observable model. It isn’t about the equals at all. Looking at where I found it, it is a code smell

As for the second part. You are also correct. It is all Javascript. There are no cell types. It’s all Javascript.

But there is a key distinction that even ObservableHQ talks about.

What comes after the equals sign is Javascript. To the left of the equals sign is not Javascript. It is a special Observable notation! They call it an Observable Cell Value Declaration, not a Javascript Assignment. This is the key to my post. You literally cannot copy and paste that to a REPL and have valid Javascript.

See here at 2:11 onwards.

The key to my post is that because of the use of an equals, many users are like “yeah, this is Javascript”, but the creators of Observable are literally saying, "No, actually it isn’t. It’s Observable, and it allows for the reactive, non-order specific nature of Observable. It is not the same as a Javascript assignment, but we try to make it look like that"

By simply changing the operator to something else similar, we can get rid of this misconception completely. Developers will actually understand what’s going on. They will understand why some Javascript can’t be copy-pasted into Observable.

I think its one of the first gotchas that we run into on observable and after some getting used to/wrapping this around our head it becomes invisible and we do not see how it is anything else, but your point of view is very welcome in this forum and this reminds me of the initial versions of observable where the cell name used to be displayed in left margin, maybe a different syntax highlight in the code editor would make it more easy to distinguish observable from javascript?

2 Likes

I heard about the old style before. I believe it was changed because small displays couldn’t see the names. When that was implemented, did we have to do these same “hacks”, if you will, or because it was separate, these were not needed?

Creating a visual distinction might help a little, but unless there is some clear distinction, I don’t see how that would mitigate much. These “hacks” would still be needed.

Syntax highlighting is one method for visual distinction. Not sure if syntax highlighting would really make a difference. Maybe it would partially. Just seems like there is too much coupling here.

Not sure if this will help (or that it responds to the type of ‘hack’ you might be after), but in case:

In this post from awhile back, @mootari provided custom CSS to put cell names back into the left margins:

@-moz-document domain("observablehq.com") {
div[id][data-node-id]:before {
  content: attr(id);
  position: absolute;
  right: calc(100% + 20px);
  top: 5px;
  pointer-events: none;
  z-index: 1;
}
}

Regrettably it doesn’t seem to be working now :frowning: If you can get it to do so, please share!

I lamented about this before on the forum, but I think it’s a shame that these labels aren’t immediately visible anymore on certain types of cells (like html and md cells… or maybe all tagged template literals).

Mike gives a clear explanation for the change, and of course, he the Observable team are pros, and I am in no place to question their technical judgement, but I think this comment merits a quote:

… And to extend this a bit more, for me it’s a bit of a challenge when I want to embed bits of a notebook with lots of little html cells that I have unpinned, and to figure out if I named them I have to click into each. Fortunately Jeremey’s Handy Embed Code Generator expedites this process for me rather substantially.

Alas! Even if these weren’t visible on some devices, there are other bits of the platform that I find really hard to see and interact with when not on a desktop (like comments)!

It’s a user style that requires a browser extension (like Stylus). It’s not meant to be put inside a notebook (if that’s what you were trying). I have it active all the time in my main browser. Whenever I view a notebook without the user style I have a harder time navigating the notebook, especially when there are many named cells that return a DOM element.

Ah! Yes, this was exactly what I was trying! Thanks for the clarification. I’ll have to check it out.

Cheers! :smile:

and @aaronkyle I actually saw that post before and tried it, but it didn’t seem to work when I tried it with one of the browser extensions like Stylish. I had used some of these user styles before, but couldn’t get this one working. Oh well.

That is also why I know it was changed because of small screens.