Chameleon Blocks.

72 views
Skip to first unread message

Mark Friedman

unread,
Oct 25, 2021, 7:03:57 PM10/25/21
to blo...@googlegroups.com
As you know, many programming languages, especially dynamically typed ones like JavaScript, Python and Scheme, have the property that any function call may or may not return a value.  Blockly makes it difficult to express such a concept, as it more-or-less forces you to choose whether a given block type has an output or not.   

A number of Blockly-based systems that I've seen have dealt with this in various ways.  One is to have a sort of expression->statement block that more-or-less eats the return value of a block.  Then you could have all your function call blocks return a value and surround it by an expression->statement block when you need to use it in a statement context.  Another technique would be to have two versions of a block for each function call; one with an output and one without.

Each of these approaches seems a little clunky to me.  The first because it adds additional effort and extra blocks to the program.  The second because it doubles the number of blocks in your toolbox.  Each approach also has a "syntactic" mismatch with the underlying textual programming language.  In the textual language it's the context that determines the behavior, not the syntax of the function call.*

I think what I would really like to see are what I'll call "chameleon" blocks, which could change shape (i.e. switch from expression to statement) depending on where it's placed.  I'm imagining a block which could change shape appropriately as you drag it near value inputs or statement connection points.  I've thought about having a context menu item on the block which would make the switch, but that still seems too clunky to me.

I'm writing this in the hope that maybe some of you have thought about this (or are now thinking about it) and have some ideas for how it could be done, ideally without having to fork Blockly or create a custom renderer.  Maybe something that hooks into the InsertionMarkerManager or BlockDragger.

Thanks in advance.

-Mark

* Ok, that's not entirely true.  Some textual programming languages (e.g. BASIC, Fortran and COBOL) do force you to use syntax to distinguish the calls but AFAIK most newer languages do not.

Coda Highland

unread,
Oct 25, 2021, 10:19:30 PM10/25/21
to blo...@googlegroups.com
I was looking into this a couple years ago for exactly the same reason. It is, in theory, possible to have blocks that have both top/bottom connections and left-side connections. I have no idea how the renderer would deal with this -- will it just work, will it give one precedence over the other, will it just barf? -- but as far as I can tell the data structures and code generation would handle it just fine out-of-the-box. I would probably have it apply a mutator to itself to get rid of the unused connections when you drop it into place in order to keep the rendering clean, and then put them back when you disconnect the block.

Maybe just give it a try?

/s/ Adam

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/CAOk7GcSSud37K0NG0w9%3DVVGimhjxXrH66DEBuQ%3D7yBUcL0fLTA%40mail.gmail.com.

Coda Highland

unread,
Oct 25, 2021, 10:21:35 PM10/25/21
to blo...@googlegroups.com
It occurs to me to mention: Blockly's code generation system would handle it fine, but the block's code generator would need to detect which form to generate by looking at its state, since statement generators and expression generators return different data types.

/s/ Adam

Beka Westberg

unread,
Oct 26, 2021, 1:27:25 PM10/26/21
to blo...@googlegroups.com
Hello!

As far as I know, there's no formal hook for doing this :/ However, the dynamic connection plugin does something similar with a monkey patch to the InsertionMarkerManager (source). (Side note: not sure why it monkey patches when that's technically not allowed for first-party plugins).

> It is, in theory, possible to have blocks that have both top/bottom connections and left-side connections.

I think this used to be allowed, but at some point it was made illegal. I think this was because it could cause circular groups of connection, but I can't remember the specific context.

Sorry that's not very helpful haha. But hopefully it gives you an idea of the current state of things.
--Beka

--

Coda Highland

unread,
Oct 26, 2021, 2:36:48 PM10/26/21
to blo...@googlegroups.com
I don't think you pointed to the right line of code. That check is six years old and I definitely haven't been using Blockly that long. The context for that specific check was part of making setOutput() work to dynamically switch blocks between connection types.

You'd have to skip past the convenience functions and manipulate the connection objects directly. `block.outputConnection = new Blockly.Connection(block, Blockly.OUTPUT_VALUE);` or something like that.

/s/ Adam

Mark Friedman

unread,
Oct 26, 2021, 7:16:37 PM10/26/21
to blo...@googlegroups.com
It almost works if I bypass the setOutput() call and make the output connection directly.  Unfortunately, I run into a couple of issues.  One is that Block.unplug (which is called in lots of places in Blockly) assumes that you either have an output connection or an input connection:
Blockly.Block.prototype.unplug = function(opt_healStack) {
if (this.outputConnection) {
this.unplugFromRow_(opt_healStack);
} else if (this.previousConnection) {
this.unplugFromStack_(opt_healStack);
}
}
;

I thought that I could maybe bypass that issue by dynamically removing the output connection if the monkey patched InsertionMarkerManager tells me I'm in a statement context (which is behavior that I wanted anyway) but the InsertionMarkerManager later makes an assumption that the connections on the block don't change on the fly and its call to Block.getMatchingConnection ends up throwing an exception related to that.  It was pretty close, though!

-Mark


Coda Highland

unread,
Oct 26, 2021, 9:05:39 PM10/26/21
to blo...@googlegroups.com
The nice thing about object-oriented code is that you can subclass it. You should be able to override this method.

That said, I also think it's not a terrible idea to remove the `else` from that function in general.

That assumption should be manageable? It's so close to working that this should be within reach.

/s/ Adam

Mark Friedman

unread,
Oct 27, 2021, 1:51:53 PM10/27/21
to blo...@googlegroups.com
Thanks, Adam, but unfortunately, I don't think that subclassing Block and overriding that method really solves the problem; blocks will still be created with the original Block class.  I think I would have to monkeypatch the unplug method (and maybe others, given the general assumption that blocks don't have output and previous connections), which I would like to avoid.  

-Mark


Reply all
Reply to author
Forward
0 new messages