According to my girlfriend, my last blog post was rather boring. So I had to remind her that she is not my audience. I am writing for an elite group of software PROFESSIONALS who are fascinated by intricate detail. Then I went back to read the first paragraph and fell asleep into my bowl of cereal.

Well, today’s post will be a bit more exciting with much hopping around. In a good way.

Today’s topics:

  • The hop symbol for relationship navigation
  • Specifying default paths for terse, easy hopping
  • Refining with selection criteria along the way
  • Hopping in place, better reflexive navigation
  • A 20:1 reduction in lines of action language

From one object to more objects

Last week we selected objects directly without traversing any of the relationships (associations and generalizations) on the class model. You want direct selection when you have access to data values that you need to convert to object references. But once you have one or more objects in hand, it is generally more efficient1 to trace relationships on the class model to find what you need.

Starting with a simple example from the Elevator Case Study, an instance of Door needs to jump across R4 to grab its associated Cabin object.

In Scrall we use the / hop symbol. Easy to type and intuitive for us Linux/Mac folks2.

my cabin .= /R7/Cabin

or use the verb phrase3

my cabin .= /is passenger entry for/Cabin

So you can freely mix relationship names, verb phrases (role names) and class names in a hop expression without any special distinguishing notation or quote marks. The Scrall language analyzer has access to the class model so it can connect the dots and figure out what you mean.

Note the boldface text in the verb phrases on the class diagram. miUML lets you specify optional verb phrase abbreviations4 to trim down your action language text. (Proper UML would use a tag, of course). So you could type:

my cabin .= /entry/Cabin

But, that’s STILL too much to type as far as I’m concerned…

Short hops

What is the point of restating what is already on the class diagram? One fact in one place, right? From a given object, it is frequently the case that you are hopping across just one association where there is only one possible destination class.

The Scrall language analyzer knows what you want.

So just type this:

my cabin .= /Cabin

The language analyzer has access to the class model and can see that there there is only one path from Door to Cabin. But not always.

If you have two or more associations between the same two classes you will need to be specific. So, from the Vehicle’s perspective, you might write:

my driver .= /driven by
my owner .= /owned by

Here are some other legal variations:

my driver .= /R1
my owner .= /R2/Person

You don’t need the class name at the end to resolve ambiguity, but you might put it in for clarity. Whatever you do, keep your style consistent. A configurable autofill feature can help with that.

This may seem like a lot of fuss for such a tiny bit of text. But we want to keep our building blocks small and readable so that we get a payoff hopping across multiple relationships, nesting expressions and doing other awesome s^%t.

A long jump

Now let’s try a longer jump.

In the Elevator Case Study all Shafts in the same Bank (think Express Bank, Freight Bank, Lower Floors Bank as examples) provide identical service. Cabin speed, door open delay and all other behavior is uniform by policy for all Shafts in the same Bank.

Upon opening at a Floor, the Door object needs to retrieve, from its associated Bank object, the amount of time it should remain open before automatically closing. Scrall let’s you say this:

open delay = /Bank.Door open delay

Just because it lets you say that, doesn’t mean you should. If we zoom out, we see that there are multiple paths between Door and Bank.

There are many loops of relationships to confuse the Scrall language analyzer. Which path to take?


R4/R2/R1 – The path we want, obvious choice… right?

R4/R53/R38/R29 – Not that one

R4/R43/R28/R1 – Nope

R4/R43/R28/R29 – Are you insane, language analyzer? No!


And so many other crazy possibilities.

The language analyzer takes the shortest path to the target if no other cues are provided. And we just happen to be lucky that there is only one shortest path. We will not always be so fortunate, however. Also, it would suck if a slight edit to the class model opened up multiple shortest paths and hosed our actions.

So, unless you are pretty sure that this is not going to happen (single or double hops are relatively safe), you may want to provide the whole path anyway.

Oh, come on! Let’s not give up that easy. You could just provide a few cues to resolve potential ambiguity:

open delay = /Shaft/Bank.Door open delay

(Shortest path to Shaft is /R4/R2 and then the hop to Bank is a single hop)

Also good:

open delay = /Cabin/Bank.Door open delay

(One hop to Cabin and then shortest path to Bank)

So you could combine the shortest path rule with just enough clues to head in the right direction. But, really, there’s so little gain in this case, that you might as well just spell the whole path out.

Grrr.

We want a more satisfying solution with less potential for confusion.

Default path names

If the metamodel is modified to support path naming on the class model, you could provide this information:

[<path name>] = <full relationship path>

This is not action language, but using the above syntax as part of the Elevator class model, you could specify this:

my = Door/R2/R4/R1

Now the name my (local to the origin class, Door) can be used in a hop expression like so:

open delay = /my/Bank.Door open delay

Here the action language is easy to read and all ambiguity is removed. The fact is, there are only a tiny subset of possible paths on a class model that are ever used. So why not name some of the longer ones and keep them all in one place?5

Notice, however, that the path name is optional! If no name is provided, the path is effectively declared as the default. That being the case, you could write our original concise statement without fear of ambiguity:

open delay = /Bank.Door open delay

To offer a comparison, in xtUML’s OAL (Object Action Language), you would have needed to write all this:

select one my_Bank related by self->Cabin[R4]->Shaft[R2]->Bank[R1];
open_delay = my_Bank.Door_open_delay;

Bonus points to anyone who demonstrates the best way to do this in ALF, TextUML or any other action language in the comments below.

Selection criteria

The selection expression discussed in my previous post may be included at the end of a hop expression to narrow down your choice of objects.

In some activity in the Bank class, you can apply selection criteria to grab all related Cabins going up (if any).

up cabins ..= /Cabin( Travel direction: .up )

Traversal via /Cabin follows the shortest path R1/R2 rule and .up is an enumerated value defined on the Direction type seen on the class diagram. So the grammatical pattern here is…

<object set>( <selection expr> )

… where the object set could be produced by an object variable, direct selection or hop expression.

Oh, but it gets better

Selection criteria may include relationship as well as attribute comparisons.

So, a Bank can select all Cabins whose doors are unlocked (open and not moving) at some Floor.

stationary cabins ..= /Cabin( Current floor: some floor,
   Door.Unlocked )

The first part of the selection criteria is a simple attribute-value comparison with the previously defined data variable some floor. The second part traverses to the related Door instance and returns true/false on the boolean attribute with the comma filling in the implicit AND.

You cannot easily assemble the stationary cabins set in OAL. Instead you would probably write one of these dreaded for-each loops which Scrall discourages:

select many my_Cabins related by self->Shaft[R1]->Cabin[R2]
   where selected.Current_floor == some_floor;
for each this_Cabin in my_Cabins
   select one its_Door related by this_Cabin->Door[R4];
   if its_Door.Unlocked
       // Do something with c here in the loop
       // (Quick, before it goes out of scope!)
   end if;
end for;

Well, that was a whole lot of ugly.6 How do you do it in your favorite action language?

Hopping along reflexive associations

Reflexive associations get special support in Scrall as we’ll see using this example:

On a reflexive association, you must specify the verb phrase in the desired selection direction.

follow ac .= /following/Aircraft

Now let’s mix in some criteria. We want the next Aircraft in the flight pattern order at the same altitude that is cleared to land.

follow ac at my level .= /following( (its.Altitude - my.Altitude)
   < same level height, /will land on/Runway )

Again, the comma serves as an implicit AND, eliminating the need for another level of parentheses. The first criteria in the selection expression compares the altitude differential to some height in a predefined variable. Note the introduction of Scrall’s my and its keywords. You rarely need them, but we are comparing the same attribute on two different objects. Even though my. is always assumed for local attributes and methods, it helps to improve readability in this particular context.

The second criteria is the relationship to Runway. If it yields the empty set, it evaluates to false, otherwise true.

Therefore, you grab all Aircraft at roughly the same altitude that are assigned to some Runway.

Repeated reflexive hopping

An interesting characteristic of reflexive associations is that they give you the opportunity to hop multiple times. With a linear sequence of objects in the same class, you often want to start at one object and keep hopping on the same reflexive association until you find another matching the desired criteria.

Rather than do this with an awkward for-loop construction that needlessly specifies a procedural sequence, we can just specify it inline with the /~/ hippity-hop symbol.

next ac with assigned runway .= /will land after/~/( /is assigned to land at )

The criteria consist of a single relationship comparison. It yields true for an Aircraft with an assigned Runway. So you’ll either get the empty set or the first Aircraft in sequence with an assigned Runway.

And don’t worry. The hippity-hop symbol knows to stop before it revisits the starting object in a potential link cycle. Yeah, I thought of that.

A 20:1 reduction in action language

I will leave you today with one last action language comparison. Here is some OAL from my Elevator Case Study that I have converted to Scrall. First, here is the original OAL:

// Bank Level
// Choose_shaft( Direction:Dir, Short_name:OUT_Shaft ) : Boolean
//
// Sets the best inservice Shaft to service a floor call
// as OUT_Shaft and returns false if none are found

// Initialize and establish outer scope
shortest_delay = 0.0;  // seconds
first_cabin = true;
param.OUT_Shaft = "";

// Select one of the cabins with the shortest delay
select many bank_Cabins related by my_Bank->Shaft[R1]->Cabin[R2];
for each this_Cabin in bank_Cabins
   select one its_Shaft related by this_Cabin->Shaft[R2];
   if ( its_Shaft.In_service )
       cab_delay = this_Cabin.Estimate_travel_delay(
           Floor:my_Floor.Name, Calling_dir:param.Dir );
	       if ( (cab_delay < shortest_delay) or (first_cabin) )
	           shortest_delay = cab_delay;
		   param.OUT_Shaft = its_Shaft.ID;
	       end if;
   end if; // in service
   first_cabin = false;
end for;

if ( param.OUT_Shaft != "" )
    return true;
else
    return false;
end if;  // False only if all Shafts are out of service
// ===

Here is the one line of Scrall (not counting comments) that gets the job done:

// Bank Level
// Choose shaft( Dir : Direction ) : Shaft

return( /Shaft( In service )/Cabin( ^-Estimate travel
   delay( Floor : /Floor.Name, Calling dir: in.Dir ) )/Shaft )

Let’s break it down.

// Choose shaft( Dir : Direction ) : Shaft

We’ve redefined the method signature a bit. There is no need for the awkward OUT_Shaft parameter since Scrall methods can return any type, be it a relation, object set, or data value. Since the type of this function corresponds to the class name Shaft7, we know it returns an object set. And since the empty set evaluates as false in Scrall, the calling activity can still test the return value as a boolean.

/Shaft( In service)/Cabin

Traverse (shortest path) to Shaft, checking the Shaft.In service boolean to narrow the selection and then continue the hop to the associated Cabins.

Now we have the working Cabins.

Cabin( ^-Estimate travel delay(
   Floor : /Floor.Name, Calling dir: in.Dir ) )

In my previous post, Aircraft( ^-Altitude ) returned all lowest flying Aircraft. We’re using the same min operator here, but applying it to a value returned by a method instead of an attribute. The method takes a destination floor and calling up/down direction and estimates, based on its current work load, how long it should take the Cabin to arrive at the Floor.

/Shaft

And then we just hop from the one8 or more Cabins with the least value back to their associated Shafts.

return( /Shaft( In service )/Cabin( ^-Estimate travel
   delay( Floor : /Floor.Name, Calling dir: in.Dir ) )/Shaft )

Whereas the OAL returns the ID of a selected Shaft, (requiring an extra unnecessary object selection by the calling function) an miUML method can return an object set. So the calling activity can do this:

best shaft .=. Choose shaft( in.calling dir )
if best shaft {
   // do stuff
}

Once again, feel free to post counter examples in your favorite action language. Though, if I see something I like, I will probably steal it.

Next post: Signals, relation variables and set operations


  1. For both platform independent modeling and implementation reasons.
  2. And possibly annoying to Windows folks.
  3. sm UMLs use verb phrases in place of role names for more clarity and precision. See my article on Articulate UML for more about that
  4. It’s not in the metamodel yet, but it will be when I push the next update.
  5. An miUML class diagram editor could provide a view layer that highlights all of the named relationship paths.
  6. Actually, it occurs to me that in OAL maybe you could define a Door_unlocked::boolean method on Cabin and invoke it in the select criteria, but that’s still ridiculous.
  7. In the rare case where you have a type with the same name as one of your classes in the same modeled domain, you will need to clarify with something like Class::Shaft.
  8. If the model is populated with at least one Bank, and it must be to work, you will always select at least one Cabin and possibly all of them.
Want to build better software faster?

Want to build better software faster?

Read about the latest trends on software modeling and low-code development

You have Successfully Subscribed!

Pin It on Pinterest

Share This