Sprites and rings

Sprite 1 says
backquote script pic

Sprite 2 says
backquote script pic (1)

Which sprite moves? Decide what you think before you try it. Then try it. Do you find the result to be what you expected, or is it surprising?

This is an experiment in aid of a conversation I'm having with Jens about what users' mental model of Snap! procedures is. I'd like to hear from people other than the half-dozen who post a lot! (Them too, but others also.) Thanks.

Since the variable is set to a block, sprite 2 should run it as if you stuck the block in the run block directly, in this case, sprite 2 should move. If you want sprite 2 to make sprite 1 to move, then you use the tell block.

After testing it out

I'm actually surprised it actually moved sprite 1. I genuinely didn't think it would do that.

edit: of course variables should keep their context (in a grey ring). It's just really fun when you figure out how it works.

Same thoughts as ego-lay_atman-bay
(without their edit comment)

Same as above.

Same as above.

Same as above. I view the variable as the blocks on their own, not with any context.

Running the variable will work:) It is very similar to my Snap! Editor, in Snap! project, that adds them to a list then runs them.

I am surprised...

Same as you and the other people in this post:

assuming foo is defined,

okay im guessing that the block acts on whichever sprite calls it, so sprite 2 would move. its almost like asking:

sprite 1:
untitled script pic (1)

sprite 2:
untitled script pic (2) *call

whos adding 2 and 3? well, the block is, if i keep a reference to
untitled script pic (3) even if i delete sprite 1 i should still be able to call 2 + 3 and get 5.

i guess the difference is that the move block doesnt do anything on its own (like mapping some intput to some output) it changes something outside of itself, and i guess the question is what does it change or how does it know what to change? i believe this depends on the blocks definition. is the object defined explicitly or passed implicitly?

well, the answer lies within source code written in javascript

javascript source code
SpriteMorph.prototype.forward = function (steps) {
    var dest,
        dist = steps * this.parent.scale || 0,
        dot = 0.1;

	if (dist === 0 && this.isDown) { // draw a dot
 		// dot = Math.min(this.size, 1);
 		this.isDown = false;
        this.forward(dot * -0.5);
        this.isDown = true;
        this.forward(dot);
        this.isDown = false;
        this.forward(dot * -0.5);
        this.isDown = true;
     	return;
 	} else if (dist >= 0) {
        dest = this.position().distanceAngle(dist, this.heading);
    } else {
        dest = this.position().distanceAngle(
            Math.abs(dist),
            (this.heading - 180)
        );
    }

    this.shadowAttribute('x position');
    this.shadowAttribute('y position');

    this.setPosition(dest);
    this.positionTalkBubble();
};

'this' refers to the caller, which can be any sprite, so im locking in my answer on sprite 2


okay well now im thinking the answer lies somewhere in here

run definition
Process.prototype.doRun = function (context, args) {
    return this.evaluate(context, args, true);
};

Process.prototype.evaluate = function (
    context,
    args,
    isCommand
) {
    if (!context) {
        return this.returnValueToParentContext(null);
    }
    if (context instanceof Function) {
        return context.apply(
            this.blockReceiver(),
            args.itemsArray().concat([this])
        );
    }
    if (context.isContinuation) {
        return this.runContinuation(context, args);
    }
    if (!(context instanceof Context)) {
        throw new Error('expecting a ring but getting ' + context);
    }

    var outer = new Context(null, null, context.outerContext),
        caller = this.context.parentContext,
        exit,
        runnable,
        expr,
        parms = args.itemsArray(),
        i,
        value;

    if (!outer.receiver) {
        outer.receiver = context.receiver; // for custom blocks
    }
    runnable = new Context(
        this.context.parentContext,
        context.expression,
        outer,
        context.receiver
    );
    runnable.isCustomCommand = isCommand; // for short-circuiting HTTP requests
    this.context.parentContext = runnable;

    if (context.expression instanceof ReporterBlockMorph) {
        // auto-"warp" nested reporters
        this.readyToYield = (this.currentTime - this.lastYield > this.timeout);
    }

    // assign arguments to parameters

    // assign the actual arguments list to the special
    // parameter ID Symbol.for('arguments'), to be used for variadic inputs
    outer.variables.addVar(Symbol.for('arguments'), args);

    // assign arguments that are actually passed
    if (parms.length > 0) {

        // assign formal parameters
        for (i = 0; i < context.inputs.length; i += 1) {
            value = 0;
            if (!isNil(parms[i])) {
                value = parms[i];
            }
            outer.variables.addVar(context.inputs[i], value);
        }

        // assign implicit parameters if there are no formal ones
        if (context.inputs.length === 0) {
            // in case there is only one input
            // assign it to all empty slots...
            if (parms.length === 1) {
                // ... unless it's an empty reporter ring,
                // in which special case it gets treated as the ID-function;
                // experimental feature jens is not at all comfortable with
                if (!context.emptySlots) {
                    expr = context.expression;
                    if (expr instanceof Array &&
                            expr.length === 1 &&
                            expr[0].selector &&
                            expr[0].selector === 'reifyReporter' &&
                            !expr[0].contents()) {
                        runnable.expression = new Variable(parms[0]);
                    }
                } else {
                    for (i = 1; i <= context.emptySlots; i += 1) {
                        outer.variables.addVar(i, parms[0]);
                    }
                }

            // if the number of inputs matches the number
            // of empty slots distribute them sequentially
            } else if (parms.length === context.emptySlots) {
                for (i = 1; i <= parms.length; i += 1) {
                    outer.variables.addVar(i, parms[i - 1]);
                }

            } else if (context.emptySlots !== 1) {
                throw new Error(
                    localize('expecting') + ' ' + context.emptySlots + ' '
                        + localize('input(s), but getting') + ' '
                        + parms.length
                );
            }
        }
    }

    if (runnable.expression instanceof CommandBlockMorph) {
        runnable.expression = runnable.expression.blockSequence();
        if (!isCommand) {
            if (caller) {
                // tag caller, so "report" can catch it later
                caller.tag = 'exit';
            } else {
                // top-level context, insert a tagged exit context
                // which "report" can catch later
                exit = new Context(
                    runnable.parentContext,
                    'expectReport',
                    outer,
                    outer.receiver
                );
                exit.tag = 'exit';
                runnable.parentContext = exit;
            }
        }
    }
};

which i dont feel like digging through right now so ill leave it at that. neat trick, seems counterintuitive. i think theres too many ways to do the same thing currenly, the ask/tell blocks, [att] of [obj], broadcasts, simply putting global variables/lists to handle communication. not sure where whats appropriate when

There's something to that. Partly the goal is to "keep simple things simple," so we maintain the Scratch idea of BROADCAST as a way to let the receiver decide whether to handle a particular message. On the other hand, we want to teach OOP with sprites as objects, so ASK and TELL serve to send a message to a particular sprite. On the third hand, ASK and TELL aren't really orthodox OOP because instead of just sending a keyword, i.e., a text string, maybe along with some input data, and let the object decide how to handle that message, ASK and TELL let you put the method code in the caller; the orthodox OOP way is to use OF, which lets you call only the sprite-local blocks that are published by the callee.

Global variables for communication are a last resort, or maybe a first resort for people coming from Scratch who don't know any better way to do it, especially because Scratch doesn't have custom reporters.

About what happens where in the source code, the answer (official from Jens) is that when a ring is executed (as it is in the SET block in this example), it turns some code into a procedure, which remembers the context in which it's created, including things like local variables but also including the current sprite, the one that's evaluating the ring. So that's when the MOVE block becomes, in effect, a MOVE SPRITE1 block.

The OF block has the magic power to turn the procedure into a new procedure with the other sprite in its context.

@everybody: The reason I posted this is that in a conversation with Jens it turned out that I totally didn't understand this point. Jens convinced me that it's the right thing, but I told him that I'm pretty sure no users understand it that way. Users think the procedure is just its code, and when that fails, they move to a model in which a sprite is wired into the motion blocks in its palette. Seeing the ring as what does the binding of a block to a sprite nobody gets, except now the people following the thread.

So, I'm not arguing to change the model, which is really pretty elegant, but I think we should do something to make it more discoverable for users. I'm not yet sure what the something is, but my first thought is that if you look at the value of FOO, it should be
untitled script pic
or maybe
untitled script pic (2)
or
untitled script pic (3)
because that can be applied to any block without relying on English grammar that happens to work for MOVE.

I should add that it's my fault that the value reported by a gray ring, i.e., a procedure, is shown with a gray ring around it. Really it should be some different visualization, a purple ring or something, to clarify that it's a procedure value rather than a request to make a procedure. I thought one new notation (rings) was going to be hard enough for Scratchers, and we could live with the ambiguity.

stklos> (lambda (x) (+ x 3))
#[closure 107b38190]

In Scheme (stklos is the dialect I have installed -- it's the least polluted by r6rs and r7rs), the result of running the lambda expression (which is what we represent with a gray ring) isn't a lambda expression, but rather a closure, which is the procedure text (the stuff in the lambda expression) combined with the environment (all the visible bindings of names to variable values). Most Scheme implementations, including stklos, don't try to display what's inside a closure, because environments are typically very heavily recursive data structures. (See SICP 3.1 and 3.2.)

P.S. I forgot one of the ways to do message passing: the BROADCAST block now optionally takes a particular sprite to be the receiver of the message. So the message is a keyword, rather than a block. This is the official correct (i.e., Smalltalk) way to do message passing. I think this capability is good to have, although using the name BROADCAST for it bothers me somewhat. (Media people these days talk about "narrowcasting" when they use what is generally a broadcast medium to send something only to selected viewers. That's a cute neologism but I wouldn't want us to use it!)

One reason we're adding this new kind of BROADCAST right now is that it works across scene changes, whereas trying to send code across scene changes is problematic (because the closures don't exist in the new scene).

Can I ask under what circumstances is binding the code blocks, to the sprite that sets the variable value, useful?

Apart from being an easier way of telling a sprite to do something.

Well, first of all, most of the time sprites don't move each other at all. If they do tell another sprite to do something, it's generally at a higher level of abstraction. So yeah, this specific example is a little arbitrary; I chose it to be simple.

Don't think of it as "the sprite that sets the variable value," which makes it sound even more arbitrary than it is. You have to think "the sprite that asked to make a procedure." It makes sense, I think, that if I (a sprite) ask to make a procedure, it's going to be a procedure that affects me.

But, now that I'm thinking about it harder, I'm not sure I understand this still. If you Make a Block SQUARE with REPEAT 4 [MOVE TURN] in it, and you make the block with Sprite(2) current, and then you switch to Sprite(3) and click the SQUARE block, it's Sprite(3) that turns, just the way everyone would expect. So I need another conversation with Jens... :~( (Not frowning about talking with Jens, which is always great, but about needing to talk with Jens because I still don't get it.)

JFI, this isn't a theoretical issue for me, It caused me a bit of head scratching in my Mayflower project unti l I worked out that I had to use one of these two methods

image

I know factually that procedure values capture the context (that the ring is 'what does the binding', so to speak), so I know Sprite1 would move.

I think that the issue is to do with the visual representation of procedure values. They display the implementation of the procedure, but there is no indication of context, so going by a 'WYSIWYG' intuition it would seem that they don't carry one.

As a side note, my pet definition of OOP (which is similar to Alan Kay's) requires dynamic dispatch. ASK and TELL strike me as not being object-oriented at all, even though you can use them in object-oriented ways; at least, it definitely doesn't encourage it. A novice would be much more likely to use them to command sprites remotely from other sprites than to send dynamically dispatched messages.

(Broadcasts are a better mechanism for dynamic dispatch in Snap!, but they come with their own downsides.)

See if you like the new BROADCAST option of specifying a particular object to get the message.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.