Why is my Card equal to another Card of the same rank but a different suit, and how do I order cards another way?
← Operators and Expressions · Ref: Q2280
Because the sameness your Card uses is hierarchy-wide: a '<=>' written by hand on a trait (or an abstract base) over what every card provides. Here the trait Ranked orders by 'rank()' alone, so the suit is not part of it and Card(5, hearts) == Card(5, spades). That is deliberate - the author of the trait said once what makes two cards the same - and a type that implements the trait cannot change it (E07182), and no E07238 is raised for the fields it adds.
To tell suits apart, put the suit into the trait's ordering: write the trait's '<=>' over rank() and then suit() (RankedAndSuited above), and SuitedCard(5, hearts) == SuitedCard(5, spades) is false.
Other views of the same cards - by value alone, or 'aces high' - are not a second '<=>'. A type has ONE sameness; every other order is a comparator function given to 'sort': 'sort by acesHigh'. Ace low is the natural order of the trait; the comparator ranks the ace as 14.
If each card type should be compared only with its own kind instead, leave the trait without a '<=>' and give each class 'default operator' (per-type sameness): it compares every field.
Example
#!ek9 defines module qa.operators.card.suit.sameness defines constant hearts <- "hearts" spades <- "spades" defines trait <?- Hierarchy-wide sameness, written by hand over what every card provides: the rank, and only the rank. -?> Ranked rank() as pure abstract <- rtn as Integer? operator <=> as pure -> other as Ranked <- rtn as Integer: rank() <=> other.rank() operator #? as pure <- rtn as Integer: #?rank() <?- The same idea with the suit in the ordering: rank first, then suit. -?> RankedAndSuited rank() as pure abstract <- rtn as Integer? suit() as pure abstract <- rtn as String? operator <=> as pure -> other as RankedAndSuited <- rtn as Integer: rank() <=> other.rank() if rtn == 0 rtn: suit() <=> other.suit() operator #? as pure <- rtn as Integer: #?rank() + #?suit() defines class Card with trait of Ranked cardRank as Integer: 0 cardSuit as String: String() Card() -> rank as Integer suit as String this.cardRank: rank this.cardSuit: suit override rank() as pure <- rtn as Integer: cardRank suit() as pure <- rtn as String: cardSuit override operator ? as pure <- rtn as Boolean: cardRank? and cardSuit? operator $ as pure <- rtn as String: `${cardRank} of ${cardSuit}` SuitedCard with trait of RankedAndSuited cardRank as Integer: 0 cardSuit as String: String() SuitedCard() -> rank as Integer suit as String this.cardRank: rank this.cardSuit: suit override rank() as pure <- rtn as Integer: cardRank override suit() as pure <- rtn as String: cardSuit override operator ? as pure <- rtn as Boolean: cardRank? and cardSuit? operator $ as pure <- rtn as String: `${cardRank} of ${cardSuit}` defines function <?- Another view of the same cards is a comparator function given to sort, never a second '<=>'. -?> acesHigh() as pure -> first as Card second as Card <- rtn as Integer: high(first) <=> high(second) high() as pure -> card as Card <- rtn as Integer: card.rank() == 1 <- 14 else card.rank() flag() -> answer as Boolean <- rtn as String: answer? <- $answer else "unset" defines program CardSuitSameness() stdout <- Stdout() fiveOfHearts <- Card(5, hearts) fiveOfSpades <- Card(5, spades) stdout.println(`Card 5 hearts == Card 5 spades: ${flag(fiveOfHearts == fiveOfSpades)}`) suitedHearts <- SuitedCard(5, hearts) suitedSpades <- SuitedCard(5, spades) stdout.println(`SuitedCard 5 hearts == SuitedCard 5 spades: ${flag(suitedHearts == suitedSpades)}`) ace <- Card(1, "clubs") king <- Card(13, hearts) two <- Card(2, spades) hand <- cat [ace, king, two] | collect as List of Card byRank <- cat hand | sort | collect as List of Card stdout.println(`by rank: ${byRank}`) aceHigh <- cat hand | sort by acesHigh | collect as List of Card stdout.println(`aces high: ${aceHigh}`)
Common mistakes
E07182 — Card implements Ranked, whose '<=>' is written by hand over rank(): that is hierarchy-wide sameness, sealed for every type that implements the trait. Card cannot declare or default its own. To include the suit, change the trait's '<=>'; for another order, use a comparator function with 'sort by'. See ek9 -h E07182.
Incorrect:
suit() as pure <- rtn as String: cardSuit default operator <=>
Correct:
suit() as pure <- rtn as String: cardSuit
Other ways to ask this
- Why is Card(5, hearts) == Card(5, spades) true when the suit differs?
- Why does a field a subclass adds not take part in the sameness of a trait?
- How do I compare cards by value, and differently with aces high?
- How do I get more than one ordering of the same type?
Coming from another language?
Java: a Comparable written on an interface or base class and used by equals can ignore the fields a subclass adds, and several orderings are separate Comparator objects given to sort. Kotlin: sortedWith(compareBy ...) for other views. EK9: the trait's hand-written '<=>' is the single sameness; other views are comparator functions given to 'sort by'.
Keywords: <=>, ==, E07182, E07238, aces high, card, comparator, hierarchy-wide, rank, sameness, sort by, suit, trait