jmoenig / Snap

a visual programming language inspired by Scratch
http://snap.berkeley.edu
GNU Affero General Public License v3.0
1.5k stars 744 forks source link

Blocks are flawed as a metaphor for OOP methods #2532

Closed rdococ closed 3 years ago

rdococ commented 4 years ago

Blocks don't have explicit receivers. In Scratch, they aren't really thought of as messages that one object might send another - that would be broadcasts, which have their own issues.

Snap!, which is trying to introduce both functional and OOP programming in an intuitive environment, has to work around this. You end up with clunkiness, such as requiring the "run" and "of" blocks to run the methods of other sprites.

run ([method []] of [receiver v]) with inputs [Hello!] <>

In the very earliest development version of Scratch (which I call Scratch 0.1), blocks could be used as OOP messages. You could take a block from the receiver's block palette and drag it into the sender's script pane, and it would still have the same receiver. In fact, there was no clear distinction between regular scripts, custom blocks, and broadcasts.

GP Blocks, which focuses more on OOP, does something more interesting. None of its built in blocks have explicit receivers, but the custom methods you create do.

If you implement a method with the same name in the sender sprite, you can just use "tell", making the syntax cleaner, but then the inputs to the method are now evaluated as if they were run by the receiver, which is problematic if you want to send things like the values of sender-local variables or attributes to the receiver.

tell [receiver v] to [method [Hello!]]

My best solution for this, although it is not perfect, is a variant of Scratch 0.1's solution - to allow you to have blocks with explicit receivers. This would be combined with allowing you to access blocks that the selected sprite does not implement - they could be greyed out to show that.

tell [receiver v] method [Hello!]

jmoenig commented 4 years ago

I - rather strongly - disagree.

there's nothing clunky about binding a function to an environment / receiver, as long as the method dispatch is dynamic and late-bound at evaluation time. This allows us to think of blocks as methods, which is the whole idea of Snap!. What you're proposing is reverting to textual symbols as soon as we're going meta or inter-object. It's what textual languages are - of course - doing. But the challenge for a visual programming language is to invent a text-free way, and that's what the blocks are for.

In Snap blocks do not have an implicit receiver, the relationship is a one-way compositional one: Sprite have methods, not the other way round. But the method look-up is late bound. That's what makes it perfectly OOP.

rdococ commented 4 years ago

I can somewhat see what you mean (although I'd be OK with some elaboration), although I'm unsure of how allowing each block to have an explicit receiver (which, by the way, can be a varying expression - the block doesn't have to know its receiver ahead of time, that would defeat the point of it) is something textual.

Maybe the issue is purely cosmetic? I don't like the clunky way you have to select a method with the "of" block and then use "run", and you can't put the inputs in the block itself without running the block and evaluating its inputs in the same context.

At the same time, having the inputs separated from the block's context is an important indicator that the inputs are evaluated in the sender's context, while the block itself is run in the receiver's context.

brianharvey commented 4 years ago

I think the part about the method's inputs is orthogonal to the question about how you find the method; you can say super set pen script pic or super set pen script pic (1) and the difference is, I think, clear and learnable.

I agree that I'd like to be able to use TELL this way even if there is no such method in the sender, but so far Jens is immovable on this point.

I confess to not understanding Jens's view well enough even to disagree with it convincingly, but calling the block (in the TELL or OF block's input) a method rather than a message doesn't help, I think. If you think it's a method, it makes sense that you can't give it inputs directly. But if you take the entire instruction as a message, which the receiver handles by calling whatever of its methods or object variables are called for, then it's easier to be flexible about its construction.

By the way, it's weird that the location-pin symbol stays with most blocks as they're dragged from the palette to a script, but not with variable blocks.

jmoenig commented 4 years ago

variables are resolved when evaluated. Therefore the location pin would be wrong on a variable block, because the same block can resolve to a global, sprite-local or script variable depending on it lexical environment. Methods, otoh, are in a separate namespace that is always local, never global. A local block can neither be resolved to a global procedure nor override a global block. We've discussed this at great length so there's no need to bring it up here.

brianharvey commented 4 years ago

I would love it if you were to write up a full description of your model of OOP written for a naive user (such as me), that is, defining things like "lexical environment" as they come up. I feel like I keep getting 2-D slices into a 3-D structure, so to speak, in these discussions.

jmoenig commented 4 years ago
scopes
brianharvey commented 4 years ago

Oh sure, but when we get internal definitions, won't it be possible to write something exactly analogous for other blocks too?

jmoenig commented 4 years ago

Maybe, I'm not sure.

rdococ commented 4 years ago

I don't think that blocks with explicit receivers are necessarily more "text-like" than blocks without them, but I do think that Scratch-like programming as a whole is similar to textual programming - just with the possibility of syntax errors removed.

brianharvey commented 4 years ago

About blocks vs. text, of course fundamentally it's the same programming: loops, objects, functions, conditionals, messages, methods, lists, all that. I do think that when we're forced to use text to specify something (as we've done all along, Jens, for sprites, costumes, etc., even if the text is in a readonly pulldown menu) it raises the question of whether something should be first class and isn't. But blocks aren't great just because they're blocks; the point (for me, ymmv) is that a visual presentation can make something self-explanatory. Hexagons for predicates, that little uparrow at the bottom of loop blocks, and of course my favorite example, the list of blocks. As soon as you see it, you understand procedure as data, without anyone saying a word.

So the question is whether we are succeeding in using the visual language to teach the idea of local methods. And, sorry Jens, questions like this come up so often, even from me let alone users, that I have to say we're not succeeding in this case. We need to find a better visual presentation.

So, for example, top of my head, what if we did allow dragging a sprite's local block into another sprite's scripting area, but when the user does that, the block's title line changes to "sprite3's foo" or whatever? And if you click on it, you get an error message along the lines of "only sprite3 can run/call this block. Use TELL/ASK to have sprite3 run/call it." (Run or call etc. depending on the shape of the block.)

jmoenig commented 4 years ago

Part of the problem with OOP is that it's been degenerated by all programming languages which aren't Smalltalk. All those dot-notation languages really don't send messages but more or less directly invoke methods on another object. And then they pass along stuff from other scopes as arguments. The whole idea of OOP was not ever to do that, and instead to treat objects as their own "computers".

Yes, I'm aware that that's not what most programmers do even today. But I've read and written so much alleged OOP code myself that in our own programming language I'm not going to facilitate crappy "best practices". I'm convinced that we're teaching exactly the right thing about local blocks. The problem is that semi- / professional programmers and half-educated teachers (think they have to) want to teach all the wrong things, and all of these crappy ideas circle around the theme of

"Controlling" sprites from a "Master script" in some "Handler" sprite.

And then they're unhappy if we don't support that. That's by design.

brianharvey commented 4 years ago

I'm convinced that we're teaching exactly the right thing about local blocks.

The problem isn't whether we're teaching the right or wrong thing. It's that our users aren't learning what you think you're teaching. And, again, that means something is wrong with the visual presentation, and we have to redesign that so it teaches what you want to teach.

So, here's another possibility: We have an EXPORT block that takes another block as input, and marks it to be usable as a message. Then, when you select a sprite in ASK or TELL, you get a dropdown list of exported blocks (including variables; I don't see why they're different in principle from other blocks) as a second input. And then there's a WITH INPUTS thing as in RUN/CALL.

I think that would be much more discoverable. So, no C-slot in TELL.

jmoenig commented 4 years ago

a drop-down - which we already have for the of block could work. But - again - it wouldn't even begin to satisfy the issues mentioned here, because that literally is the first example in this thread. I'm more and more convinced that encouraging kids (and adults!!!) to think about sprites as modules and to regard events such as broadcast as the primary way for modules to communicate is what's needed, and that things like tell is only there for exceptional cases and not something to be used mindlessly, because it links sprites together in a way that destroys their independence.

brianharvey commented 4 years ago

I don't have a clue about the difference between "modules" and "objects." Sprites take messages and run methods. I just think TELL as an abbreviation for RUN (-- OF --) is fine. Instead of grossly overloading OF, we have TELL and ASK to send messages to sprites.

BROADCAST is yucky, unless you really mean for all sprites to get a message. TELL and ASK are just like BROADCAST to a single sprite! Except with BROADCAST you have no idea if the sprite you're interested in accepts that message. Better to have the messages in a pulldown. And I think it's fine if you can drop a list of sprites onto the first (dropdown) input of TELL/ASK.

jmoenig commented 4 years ago

I think it's fine if you can drop a list of sprites onto the first (dropdown) input of TELL/ASK

Hach! See, this is why such discussions - as all discussions about OOP - are so problematic. This sounds like a cute idea until you think about what it really means, and then you'd notice that in the case of ask dropping a list of sprites onto the first input is utterly problematic. And then, when you think about it some more...

brianharvey commented 4 years ago

... but I also think you're worrying too much about information hiding. There's an example like the thing you don't like in BJC: say (knock knock) for 2 secs tell (other sprite) (say (who's there) for 2 secs) say (Sam) for 2 secs tell (other sprite) (say (Sam who?) for 2 secs) say (Sam and Janet Evening!) for 2 secs) tell (other sprite) (say (groan...) for 2 secs)

Which reminds me, we should really have an "AND WAIT" option in TELL.

Anyway, the point is, it should really be Say (knock knock) tell (other sprite) (who) where the other sprite has a WHEN I RECEIVE (WHO) block. But that leads to a lot of tiny one-line scripts in the two sprites. So I can sympathize with people who use TELL ... (SAY...).

brianharvey commented 4 years ago

Our comments crossed... About ASK to a list, clearly it should collect the results in a list, preferably in the same order as the list-of-sprites input. It might be a bad idea for performance reasons, but it's really just syntactic sugar for MAP (ASK ( ) FOR (expression input)) OVER (list of sprites).

jmoenig commented 4 years ago

Which reminds me, we should really have an "AND WAIT" option in TELL.

See! You did it again!

tell does wait. take our course!

brianharvey commented 4 years ago

Yeah, but the point is, maybe sometimes it /shouldn't/, so that "AND WAIT" should be optional.

jmoenig commented 4 years ago

launch

jmoenig commented 4 years ago

Jadga and I made a course for that.

brianharvey commented 4 years ago

Okay, fine, I guess launch will do. But, hmm, sometimes I want to wait until they're all finished, like BROADCAST AND WAIT, but I don't want to wait until the first one finishes to start the second. So I can't do (FOREACH (sprite) IN (list of sprites) (LAUNCH (TELL ( ) TO (script)))) because then I can't find when /all/ the sprites have finished. If I had an AND WAIT option on TELL, it would handle the waiting until they all finish business. Otherwise I have to make ugly global count variables.

jmoenig commented 4 years ago

there's a library for that. Parallelism. And it doesn't use an ugly count variable.

brianharvey commented 4 years ago

Oh yeah, I forgot about that. Very cute! I'm going to bed...

DyslexicAwe commented 4 years ago

Let me begin with saying that I 100% agree with Jens that "tellis only there for exceptional cases and not something to be used mindlessly, because it links sprites together in a way that destroys their independence." It is my experience that I'm tempted to use tell/ask block only because I hate to drag the "WHEN I RECEIVE < message >"-block-containing-scripts manually as many times as there are other sprites that I want to receive the message. I guess it is not only me who want to avoid repetitive manual work, and hate to manually copy-paste the "WHEN I RECEIVE < message >"-block by dragging many times, as Brian notices, a script from one sprite to the (multiple other) sprites 'scripting areas'.

Could there be a win-win solution? I think it could. What if IDE enabled "automagical" multiple copy-pasting of scripts from one sprite to multiple other sprites, the (somehow, I dunno, graphically?, or in a dialog maybe?) selected ones only.

EDIT (an illustration how might the multiple copy-paste to... look like):

multiple_copy-paste_instead_of_dragging

Because that would help users learn (in a user-friendly way) exactly what Jens wants users to learn in the first place.

jguille2 commented 4 years ago

Hi everybody,

I also agree with Jens and the idea of late-bound at evaluation time, perfect with tell, ask...

And so, I don't need quick changes. But perhaps, someday we can explore new possibilities... but it is a subject that requires finding its moment (with no hurries and not looking at isolated details).

For that moment, I want to write down my example where sometimes I miss receivers information:

Yes, I don't need recievers information everywhere. It will be annoying! The great visual metaphor is that code4 in the Sprite1 scripting area (or palette) is clearly a block for this sprite. And if I drag and drop it to other sprite, the receiver changes (great!), and global-local blocks behavior are well resolved.

So, I only need this receiver info in some cases (for example, watchers and variables visual evaluation)

Thanks,

Joan

rdococ commented 3 years ago

I think that if someone wants to program any complex interactions between sprites, they will need a mechanism that supports arguments, passing arbitrary data and the ability for multiple interactions to occur at once. Broadcasts provide none of these features, while blocks as methods provide them all. I think this is precisely the reason why people avoid using the broadcast mechanism in Snap!, and opt instead to use "tell", "ask" and "of" with method blocks.

brianharvey commented 3 years ago

Good timing on reviving this thread, because it happens that I was recently (in the Colors and Crayons library) bitten by the thing that Joan mentioned two years ago in the comment two up from this one. So presumably two years ago I knew that, but I didn't know it two months ago.

Every so often I have a fight with Jens because I think he thinks users can read his mind, and I'm afraid this may become another of those. I hate when that happens, but I kind of view myself as the canary in the coal mine; if something confuses me it'll probably confuse users, too.

I have always known that Snap!, like Scratch, considers certain primitives (but not others) to be implicitly sprite-local. But in almost all cases we bend over backward to hide that from users! You can drag a script containing a MOVE block from one sprite to another, via the sprite corral, and we invisibly change the copied block to that other sprite's own version of the primitive. We encourage users to think of MOVE, etc., as global, not as sprite-local. If we consistently wanted to teach users that the motion blocks (and the pen blocks, as I found out to my dismay) are sprite-local, then instead of invisibly changing copied blocks, we would visibly preserve the copied block's original target sprite, so in the script in the other sprite the block would look like SPRITE1, MOVE 10 STEPS or some such thing. But no, we treat the localness of those blocks as an implementation detail we don't expect users to know about. In Scratch, the illusion of globalness is maintained perfectly, because they don't have lambda. But when you put a block in a ring, then we don't automatically change the receiver to be the sprite that runs the block.

No, even that's not true; I just experimented, and ringifying a block doesn't bake in its receiver; dragging the ringified block onto another sprite in the sprite corral changes its receiver. It's only if you store the ringified block in a variable or in a list that its receiver is baked in.

So, Jens, I don't see how you expect users to understand this behavior. If we consistently exposed the fact that some of the primitives have an implicit receiver (i.e., make that explicit rather than implicit), users would learn that. If we consistently pretended to users that those primitives are truly global, users would learn that. But when there's just this one narrow case in which we expose the implicit receiver to users, that's a misfeature.

I would prefer to live in a world in which all the primitives were truly global, just as they appear to users. But I could live in a world in which some primitives were consistently, visibly sprite-local. But (as you learned just now about the Colors and Crayons library) I mess up in a world in which the sprite-localness only shows up to users in this one obscure situation.

The same is true about the difference between TELL and RUN ( OF ). Even after re-reading this thread, I don't understand why you hate TELL. Back when TELL was a tool-library block implemented using OF, it was better. It did what I believe users expect.

jmoenig commented 3 years ago

RE:

dragging the ringified block onto another sprite in the sprite corral changes its receiver

Has the discussion really deteriorated to this level?

brianharvey commented 3 years ago

Sorry, Jens, but insulting me with no explanation isn't going to increase my understanding any.

jmoenig commented 3 years ago

evaluating a ringed block returns an anonymous function, these capture their environment, including the sprite they're made from.

copying a ringed block does not copy the anonymous function (which is an artifact of evaluating the ring) but the expression that makes a function. I don't have to explain this to you, right?

Back when TELL was a tool-library block implemented using OF, it was better

tell literally is just that, it uses of internally. Look it up! You're imagining something that isn't.

brianharvey commented 3 years ago

I don't have to explain this to you, right?

Apparently you do. The question is whether you think the users understand this model better than I do.

brianharvey commented 3 years ago

tell literally is just that, it uses of internally. Look it up! You're imagining something that isn't.

Am I misremembering a fairly recent discussion in which you explained to someone (not me, I think) why they couldn't do something or other with TELL, but had to use OF instead?

jmoenig commented 3 years ago

if they want to use the dropdown to select a sprite-local block of another sprite. We - currently - only have this dropdown in the of block, not - yet - in tell or ask. The semantics are the same, though.

Snap! blocks have no such thing as an "implicit receiver", they're just blocks, expressions, like text in one of those other programming languages. The only difference to text is that you can only access blocks - i.e. expressions, i.e. function invocations - from the palette of whichever sprite you're currently editing. That's a huge limitation if you want one sprite to control another one, which his what this thread is all about. Because in order to control the callee the caller has to somehow get hold of an instance of an expression which the callee understands.

So why don't we just let users access the union of all existing blocks from all sprites everywhere? That's a design decision, and we usually disagree on those. I'm convinced that offering all blocks everywhere would lead to an abundance of frustrating out-of-scope errors in cases where kids don't intend any inter-sprite control flow. Also, it would break modularity for sprites. And - I know this gets more controversial the more I write about it - I don't want to encourage remote-controlling one object from another, because such architectures don't scale, especially not in a system with habitual and almost ubiquitous parallelism such as Snap. Traditional OOP folks (and, I guess, FP aren't much different) are so not used to thinking parallel threads and "ecosystems" of actors, and just quickly want to jump to game-loops and "frames" where one "controller" handles everything else. But that doesn't work well with parallelism, and it's pedagogically problematic, because it forces kids to break up their scripts into artificially small units governed by the technicalities of yielding control ("frames") rather than coherent actions. But this is getting out hand. This is our system, we have complete control over what it does and should do, we can technically implement whatever we want. I think we're doing an okay job balancing expressive power and cognitive load so far, perhaps focusing a tad too much on expressive power already.

brianharvey commented 3 years ago

Hmm. I'm going to have to think a lot about this!