-
Notifications
You must be signed in to change notification settings - Fork 0
MorphingStrategies
This lesson deals with morphing strategies. A morphing strategy is a key concept in Morpheus, which determines the composition of morphs. In the following paragraphs we will see how to create and use morphing strategies to control a morph's shape.
Let's begin with the two-dimensional morph that we used in the previous lesson:
val contactKernel = singleton[Contact
with (ContactRawPrinter or ContactPrettyPrinter)
with (StandardOutputChannel or MemoryOutputChannel)]
var contact = contactKernel.!
contact.firstName = "Pepa"
contact.lastName = "Novák"
contact.male = true
contact.nationality = Locale.CANADA
The morph model consists of four alternatives, which are sorted in the following order:
Contact with ContactRawPrinter with StandardOutputChannel
Contact with ContactPrettyPrinter with StandardOutputChannel
Contact with ContactRawPrinter with MemoryOutputChannel
Contact with ContactPrettyPrinter with MemoryOutputChannel
The ordering is determined by the structure of the morph model tree. This tree consists of three types of nodes: fragments (leaves), disjunctors (or
) and conjunctors (with
). We can attach indices to the fragment nodes growing from left to right:
Contact(0) with (ContactRawPrinter(1) or ContactPrettyPrinter(2))
with (StandardOutputChannel(3) or MemoryOutputChannel(4))
Then the set of alternatives can be written as this: {(0, 1, 3), (0, 2, 3), (0, 1, 4), (0, 2, 4)}
. It allows us to define the ordering of the alternatives in the set quite easily just by comparing indices from right to left between two alternatives. The comparison of two alternatives goes from the common right-most index to the left.
In other words, given two alternatives A1={a1, b1, c1}
and A2={a2, b2, c2, d2}
, then A1
precedes A2
if and only if relation (c1 < c2) || ((c1 == c2) && (b1 < b2)) || ((c1 == c2) && (b1 == b2) && (a1 < a2))
is true.
Applying this rule to our morph model, we obtain the following ordering of the alternatives:
{0, 1, 3} < {0, 2, 3} < {0, 1, 4} < {0, 2, 4}
Now, when we know how to index individual alternatives, we can start asking how to pick a particular alternative by its index and to compose a morph according to it.
The answer is a two-step procedure. The first step is to create the so-called promoting strategy, and the second step is to invoke the remorph(strategy)
method on the morph and pass the strategy to it. The remorph method is declared in the reflecting trait org.morpheus.MorphMirror
, which is implemented by all morphs.
The promoting strategy can be created by means of the promote
macro in the following way:
var altNum: Int = 0
val morphStrategy = promote[contactKernel.Model](altNum)
The type argument of the macro specifies the morph model and the other argument is a function returning the index of an alternative. Although the code above looks as if it passes a value to the macro, the altNum
expression in the argument actually gets converted to function ()=>Some(altNum)
. This function is called whenever the strategy is asked to pick an alternative.
We can call the altsCount
method on the strategy to obtain the number of alternatives being handled by the strategy:
println(s"There is ${morphStrategy.altsCount} alternatives")
Once we have the strategy we can pass it to the remorph
method on the contact morph.
contact = contact.remorph(morphStrategy)
contact.printContact()
Since the altNum
variable is initialized by 0, the new contact is composed from the first alternative. Any following remorphing by means of the overloaded no-args remorph
method will be driven only by the value of that variable:
altNum = 1
contact = contact.remorph
contact.printContact()
Since indexing alternatives in the whole model can become unwieldy, it is possible to split the one big strategy to more ones and connect them to form a stack of strategies. Typically, we create one strategy for each dimension in our model. The following code snipper shows that:
var printerCoord: Int = 0
var channelCoord: Int = 0
val morphStrategy1 = promote[ContactRawPrinter or ContactPrettyPrinter](RootStrategy[contactKernel.Model](), printerCoord)
val morphStrategy2 = promote[StandardOutputChannel or MemoryOutputChannel](morphStrategy1, channelCoord)
In the previous code we use an overloaded version of the promote
macro, whose type arguments specifies the type of a sub-model of the main morph model. There two other arguments: the first accepts a parent strategy and serves for stacking the strategies. The second argument accepts a function for selecting an alternative. It is important to stress that the selector function works in the scope of the morph sub-model specified in the type argument. In our example the two selector functions return indices from range (0, 1).
We use the second strategy as the top of the strategies stack and we pass it as the argument to the remorph
method.
contact = contact.remorph(morphStrategy2)
contact.printContact()
The morph's alternative form can now be easily controlled independently by two coordinates (printerCoord, channelCoord)
.
printerCoord = 1
channelCoord = 1
contact = contact.remorph
contact.printContact()
####Morpheus Tutorial####
- Modelling simple entity
- Adding some behavior
- Abstracting fragment
- Reusing fragment
- Using fragment in Java
- Multidimensional morphs
- Morphing strategies
- Mutable morphs
- Delegating and sharing
- Dimension wrappers
- Fragment wrappers
- Using a morph as a fragment
- Kernel references
- Kernel references use cases
- Promoting, masking and rating