34 Alternatives

The alternative operator | combines result-producing expressions. It is most useful when the same idea can be reached through several shapes.

For example, a variant type can define a calculated attribute by giving one expression for each case:


variant Shape
{
    { Real radius } sphere;
    { Real side } cube;
    { Real radius, Real height } cone;

    → Real volume =
        sphere.(4/3*pi*radius^3)
      | cube.(side^3)
      | cone.(pi*radius^2*height/3);
}

For each shape, exactly one effective current variant base attribute applies, so the corresponding branch contributes the volume. The result is a single Real value.

Similarly, an assessment may be an exam, an assignment or a project. Each kind of assessment has a maximum mark, but the path to the maximum mark depends on the variant attribute:


u.course.assessment.(
    exam.maxMark
  | assignment.maxMark
  | project.maxMark
)

For each assessment, whichever branch applies contributes the maximum mark. The three branches all produce one result of the same type, so the alternative expression exposes one result.

Naming the result

The result can be named with as, just like any other single output:


u.course.assessment.(
    (exam.maxMark | assignment.maxMark | project.maxMark)
      as maxMark
)

The name belongs to the result of the whole alternative, not to only the final branch.

Alternatives are not union

The operator | is different from ⊔. The ⊔ operator combines path frames. The | operator combines single-result expressions and exposes one result.


u.course.assessment.(
    exam.maxMark
  | assignment.maxMark
  | project.maxMark
)

This should be read as an expression-level choice of result. It is not asking for a tuple with three attributes, and it is not joining three paths side by side.

Using alternatives in calculations

Alternatives can appear inside calculations. The following expression calculates the weighted maximum mark for each assessment:


u.course.assessment.(
    (exam.maxMark | assignment.maxMark | project.maxMark)
  * (exam.weight | assignment.weight | project.weight)
  / 100
    as weightedMaxMark
)

The example schema makes this easy because every assessment variant attribute has maxMark and weight. The path expression does not need a special case statement; each branch is an ordinary expression component.

When the branches differ

Branches do not have to keep the same output name. If branch names differ, the alternative still exposes one result, but the result is anonymous unless it is renamed:


u.course.assessment.(
    (exam.durationMinutes | assignment.wordLimit | project.estimatedHours)
      as size
)

This can be useful when the alternatives are deliberately being normalised into one common measure. The name size is supplied by the outer as.

A gotcha about repeated alternatives

Separate alternatives are separate path tails. In the weighted mark example, the disjoint variant attributes make the intended pairing clear: an exam mark is paired with an exam weight, an assignment mark with an assignment weight, and a project mark with a project weight.

Do not generalise that into a rule that repeated alternatives always share the same witness. As with calculations, repeated multivalued path tails can create combinations. When several values must come from the same place, navigate to the shared prefix first, then pick the values below it.

Composability

Alternatives compose with filters, tuple construction and folds. The following expression sums the maximum marks available from all assessments in each course:


u.course.(
    code,
    assessment.(
        exam.maxMark
      | assignment.maxMark
      | project.maxMark
    ).Σsum as totalMaxMark
)

The path reaches each course, reaches its assessments, chooses the appropriate maximum-mark path for each assessment, sums those marks, and names the result.