Executing commands#

We’ve seen that the same name might refer to several different commands, and also briefly touched on how we can refer to a specific command. But when we’re using commands, how exactly does Crochet decide which one to pick and execute? In this section we’ll dive deep into the answer to that question.

Selection and application#

When we use a command in Crochet, we only refer to it by its name. For example, in 1 + 2 we’re only telling the system “hey, I want to use the command _ + _ with these two values”. But we have no way of specifying which of the _ + _ commands we’re talking about.

Many programming languages don’t have this problem, because a name refers to exactly one entity—one function, one procedure, etc. Even in Crochet, we don’t have this problem with types because a name uniquely identifies a single type. If we say 1 is integer in Crochet, there’s only one type that can go by the name integer, so the system immediately knows what we’re talking about.

So how does Crochet figure out which command we mean? Well, it doesn’t. It does not have enough information to be able to figure out what you wanted to happen. Instead, it does its best to approximate the most likely meaning from the information available. And then execute the command.

This two-step process consists of selection—trying to figure out which command best approximates your intention, given just a name and some values. And application—the act of actually executing the command. The combination of these two processes is called dispatch.

The selection algorithm#

To make sure the approximation Crochet uses is more likely to match your expectations, Crochet uses a well-defined algorithm for determining which command to pick. This shared understanding of how selection works does not mean that Crochet can’t do confusing things—rather, it means that you’ll be able to understand why something confusing happened, and then help Crochet pick the command that more accurately captures what you meant.

Crochet’s selection algorithm first picks some candidate commands, by looking at which commands have their requirements fulfilled by the values being used. Then it uses the idea of “closeness” to rank these commands. It then picks up the single top-ranked command to execute.

But what’s “closeness”? Every value in Crochet is associated with a type. And types, which commands use in their requirements, are organised in a hierarchy. The closest commands are, then, the ones with the more specific requirements.

Let’s see this in a less abstract way. Let’s say we’re describing plants in Crochet—the way we talk about plants often suggests some hierarchy, even if we don’t think about it that way. In Crochet, we could capture this plant-hierarchy (or taxonomy) in the following way:

type plant;
type flower is plant;
type rose is flower;
type white-rose is rose;
type red-rose is rose;

So, from this declaration we know that a value such as new red-rose is a red-rose. But it’s also, less specifically, a rose, a flower, and a plant. And, of course, if we talk about any thing, sure we would be including red roses!

Notice how we have some concept of which of these terms more closely (or more specifically) describes the new red-rose value, and which ones less closely do. Sure we can point at a red rose and say “this is a plant”. But that’s not the most specific way we can refer to it, and a plant could be one of several things. If we say: “plants may or may not have thorns” that’s not exactly useful when we’re only thinking of red roses. “red roses have thorns” is a more useful statement. It’s a closer statement, in Crochet’s sense.

So if we had commands that had requirements in this hierarchy, they could look like this:

command any has-thorns = "that's a possibility";
command plant has-thorns = "maybe";
command flower has-thorns = "maybe";
command rose has-thorns = "yes";
command white-rose has-thorns = "definitely";
command red-rose has-thorns = "you bet";

And if we were to use this _ has-thorns command with our red rose, that is, if we had (new red-rose) has-thorns somewhere in our code, then Crochet’s selection algorithm would first filter these commands. In this case, we filter out the white-rose has-thorns command—because a red rose can never be a white rose, given the hierarchy we’ve described.

Everything else is still a candidate for being executed, however, so Crochet proceeds to rank these commands in the following way:

1.   red-rose has-thorns
2.   rose has-thorns
3.   flower has-thorns
4.   plant has-thorns
5.   any has-thorns

We’ve got one command at the very top—ranked in the 1st position: red-rose has-thorns. This what Crochet considers the closest command that matches what we likely meant by (new red-rose) has-thorns, so that’s the one it will pick.

Ranking trait requirements#

In addition to type requirements, commands in Crochet may also have trait requirements. Unlike types, traits in Crochet are not arranged into any hierarchy, so the idea of closeness that we’ve previously discussed doesn’t really apply to traits. Even worse: traits can be implemented by multiple types and it isn’t obvious how to think about which of these implementations and requirements should be considered “closer”.

A trait requirement has the form of Variable is type has trait, ..., so we still apply the same ranking for type hierarchies that we’ve seen before for the is type part. For the trait part, however, Crochet assumes that none of them is closer than any other of them. They all have the same weight, the same specificity, the same closeness.

However, Crochet does define that a requirement that includes traits is a tiny bit closer than a requirement that does not include traits, if both of them share the same type requirement. For example:

command (X is red-rose) smell = "It smells quite nice!";
command (X is flower has perfume) smell = "It smells nice";
command (X is flower) smell = "You can't really tell much";

Here, if we were to rank these commands we’d end up with:

1.  (X is red-rose) smell
2.  (X is flower has perfume) smell
3.  (X is flower) smell

Note how, despite being pretty much the same type requirement, the _ smell command with a trait requirement ranks slightly higher than the one without.

The number of traits in a trait requirement, or the specific traits that figure in it do not affect the ranking. Crochet only cares if a requirement includes traits or not.

Selection with multiple requirements#

We’ve seen how commands can be ranked when we have just one requirement. But things get a bit more complicated when we have multiple requirements. For example, consider what happens if we extend our collection of rose commands to consider multiple of them at the same time:

command combine: rose and: flower = "A";
command combine: flower and: rose = "B";

Intuitivelly, we’d expect these commands to have the same requirements. They both require one rose and one flower on each side. So, if we were to use this command like so:

combine: new red-rose and: new white-rose;

Which one of the commands would be selected? What would we get back from it? In Crochet, the answer would be "A". That’s because Crochet considers the left-most requirements (the first one in this case) to have more weight than the right-most requirements (the second one in this case).

So when making a decision to rank these commands, Crochet first looks at the first requirement of each command and picks the closest one. Then, if there are still multiple commands that can be selected, it looks at the second requirement of each command to pick the closest one again. And it keeps doing so until it runs out of requirements, or only one top ranked command remains.

So we’d rank the commands above like this:

1.  combine: rose and: flower
2.  combine: flower and: rose

Ambiguities in selection#

Sometimes selections are “too successful”. That is, with all the information that Crochet has, it isn’t able to pick just one top ranked command—it is left with several top ranked ones. In these cases Crochet simply cannot proceed on its own and needs some manual intervention.

But how do these kinds of ambiguity rise? There are three common cases:

  • Uncoordinated definition of commands: commands can be defined anywhere, by anyone, for any type. So it might be the case that you’ve defined a command for some type with a name, and someone else independently defined another command for the same type, with the same name.

    Here, you need to pick which of the commands the application will use.

  • Coding mistakes: because commands can be defined anywhere, one might forget that they had already defined a command for some type and try to define it again somewhere else.

    Here, you might want to remove one of the command definitions. Or, if they’re both supposed to exist, you can rename one of them.

  • The use of traits: because traits don’t have a hierarchy, they’re particularly prone to ambiguities. In these cases you can try to make the requirement more specific or rename the command.

What if no command is selected?#

Sometimes the selection might not be successful at all—there are no commands whose requirements are fulfilled by the used values. Again, Crochet can’t really do much here on its own and will need manual intervention.

There are three common cases where this failure happens:

  • Mistyping the command: it’s easy to have a typo in the command name and not notice it. Sometimes these typos can mean that you get a command that exists, but wasn’t the one you wanted.

    In either case, the way to address this is to fix the typo.

  • High requirements: it can be that, when you started writing the program, you had some view of the requirements in mind, but as you’ve iterated in your design you started relaxing those requirements—but didn’t get to update them in all of the places.

    In this case you might want to update the requirements in those commands.

  • Missing commands: sometimes you might approach writing a program through a more wishful approach—you use commands before you actually define them; and you only define commands when you’re happy with how using them feels like. It’s easy to forget to define the command later, and get this error when trying to run the program.

    Another case where this can happen is if you’re using a package written by someone else, and you try to provide values to a command that the package author had not considered when designing it.

    In both of these cases the only action you can do is define the missing commands.

Applying commands#

We’ve selected one top ranked command, so Crochet can proceed to applying that command—executing its behaviour. Application of commands in Crochet is largely similar to how it happens in other programming languages: we allow the names described in the command’s signature to refer to the values we’ve used in that position, and then we execute the command’s body.

That is, consider the following command:

type person;
type alice is person;

enum food = bread, cake, crepe;

command (Who is person) likes: (What is food) =
  "[Who] likes [What]!";

If we use this command like so:

alice likes: cake;

Then, inside of that command’s body, the variable Who will refer to the value alice, the the variable What will refer to the value cake. The names refer to the values that appear in the same position when we use the method. This means that, as an output, we’ll get "alice likes cake!"

More specifically, we call the names in the signature, like Who and What, parameters. The values that appear when we use the command, like alice and cake here, are called arguments.

The self name#

In Crochet, there exists one very special name called self. This name will always refer to the left-most argument in an application, as long as this argument appears before any portion of the command’s name.

That is, in _ some-command, _ + _, and _ some: _ keyword: _, the argument in the position of the first underscore can also be referred to by the special name self. But this isn’t true for the command some: _ keyword: _, because the first underscore here appears after a portion of the command’s name.

Using self, both of the following commands have equivalent behaviour:

command alice greet = "[self] says, 'Hello!'";

command (Who is alice) greet = "[Who] says, 'Hello!'";

You might have stumbled with the concept of self in other languages by different names. It’s often called “the receiver parameter”. Sometimes it’s also called by the more confusing term “the method context”. Some languages use me or this for this same concept. Ultimately, in Crochet, self is just a convenience that relieves you from naming the first parameter explicitly.