Why does my class get only '==' and no '<' when one of its fields is an open class (E07620, E07188)?
← Operators and Expressions · Ref: Q2281
An open (or abstract) class can be extended, so a field of that type may hold a value of the class or of any sub-type, and two values of different run-time types are never in order: their '<=>' is unset. A '<=>' over such a field would answer unset for two set values, and then '<', 'sort' and '<?' would depend on which operand came first or how the list arrived.
So EK9 does not guess:
- the bare 'default operator' leaves the '<=>' out. The class keeps '==' and '<>', field by field, and using '<', '<=', '>', '>=', 'sort' or '<?' on it is E07620;
- a '<=>' you write by hand over that field ('item <=> other.item') is E07188, reported at the declaration;
- an explicit 'default operator <=>' over it is E07200.
The same happens with a Money or Dimension field (Q2262), which only order within one currency or unit.
Your choices:
1. Keep the equality and state the order you want as a comparator function: 'sort by byItem'. It compares a part every kind has (here the label), so it always answers.
2. Write the class's '<=>' by hand over such a part ('itemLabel() <=> other.itemLabel()', with a hand-written '#?').
3. Make the field's class closed (remove 'as open') if nothing needs to extend it, or give its abstract base or trait a hand-written '<=>' (hierarchy-wide sameness, Q2243): then any two values compare.
Example
#!ek9 defines module qa.operators.open.field.ordering defines class <?- An open class: a value of it may really be a sub-type, and values of different run-time types have no order. -?> Product as open name as String: String() Product() -> productName as String this.name: productName label() as pure <- rtn as String: name default operator <?- The bare 'default operator' cannot order a Product field, so Entry gets '==' (field by field) and no '<=>'. -?> Entry item as Product: Product() count as Integer: 0 Entry() -> item as Product count as Integer this.item: item this.count: count itemLabel() as pure <- rtn as String: item.label() quantity() as pure <- rtn as Integer: count default operator defines function <?- The ordering you want is yours to state, with a comparator function: by the label of the item. -?> byItem() as pure -> first as Entry second as Entry <- rtn as Integer: first.itemLabel() <=> second.itemLabel() defines program OpenFieldOrdering() stdout <- Stdout() pens <- Entry(Product("pen"), 3) samePens <- Entry(Product("pen"), 3) paper <- Entry(Product("paper"), 1) stdout.println(`same entry: ${pens == samePens}`) stdout.println(`different entry: ${pens == paper}`) entries <- cat [pens, paper] | sort by byItem | collect as List of Entry for entry in entries stdout.println(`by item: ${entry.itemLabel()}`)
Common mistakes
E07620 — Entry has a field of the open class Product, so its bare 'default operator' gives it '==' but no '<=>': '<' is not available on it. Compare with a comparator function (sort by byItem) or write '<=>' by hand over a part every Product has. See ek9 -h E07620.
Incorrect:
stdout.println(`less: ${pens < paper}`) entries <- cat [pens, paper] | sort by byItem | collect as List of Entry
Correct:
entries <- cat [pens, paper] | sort by byItem | collect as List of Entry
E07188 — Product is open, so two items of different run-time types have an unset '<=>', and Entry's '<=>' built on it can answer unset for two set Entry values. Compare a part every kind has (the label), as the comparator byItem does, instead. See ek9 -h E07188.
Incorrect:
quantity() as pure <- rtn as Integer: count operator <=> as pure -> other as Entry <- rtn as Integer: item <=> other.item operator #? as pure <- rtn as Integer: #? item override operator ? as pure <- rtn as Boolean: item?
Correct:
quantity() as pure <- rtn as Integer: count default operator
Other ways to ask this
- Why is '<' not available on a class that holds a field of an open type?
- Why is my hand-written '<=>' over a field of an open class refused (E07188)?
- Why does the bare 'default operator' leave out '<=>' when a field type can be extended?
- How do I sort objects that hold a field of an open or abstract type?
Coming from another language?
Java: a compareTo over a field of a non-final class compiles, then misbehaves or throws only when two sub-types meet at run time. EK9 decides this when the class is compiled: the ordering is withdrawn (E07620) or refused (E07188), and the equality remains.
Keywords: <=>, E07188, E07200, E07620, comparator, default operator, field, open class, sameness, sort by