Search code examples
f#comparison-operators

Implementing F# comparison on disciminated unions


I have a type for logging levels:

type LoggingLevel =
| Trace
| Debug
| Info

I would like to say that some logging levels are higher than others. For example, Trace is higher than Info.

So I implemented IComparable like this:

[<StructuralEqualityAttribute>]
[<CustomComparisonAttribute>]
type LoggingLevel =
| Trace
| Debug
| Info

  interface IComparable<LoggingLevel> with
    override this.CompareTo other =
      let score x =
        match x with
        | Trace -> 0
        | Debug -> 1
        | Info -> 2
      (score this) - (score other)

But when I try to use it, I get an error:

if a >= b 
then 
  // ...

The type 'LoggingLevel' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface

How have I gone wrong here?


I managed to get it working, but now the type definition is so verbose! There must be a better way...

[<CustomEquality>]
[<CustomComparisonAttribute>]
type LoggingLevel =
  | Trace
  | Debug
  | Info

  override this.Equals (obj) =
    match obj with
    | :? LoggingLevel as other ->
      match (this, other) with
      | (Trace, Trace) -> true
      | (Debug, Debug) -> true
      | (Info, Info) -> true
      | _ -> false
    | _ -> false

  override this.GetHashCode () =
    match this with
    | Trace -> 0
    | Debug -> 1
    | Info -> 2

  interface IComparable<LoggingLevel> with
    member this.CompareTo (other : LoggingLevel) =
      let score x =
        match x with
        | Trace -> 0
        | Debug -> 1
        | Info -> 2
      (score this) - (score other)

  interface IComparable with
    override this.CompareTo other =
      (this :> IComparable<LoggingLevel>).CompareTo (other :?> LoggingLevel)

Solution

  • I would like to say that some logging levels are higher than others. For example, Trace is higher than Info.

    Do you need to use custom equality and custom comparison at all? F# has these built in for Discriminated Unions. You just need to write them in increasing order in the type definition:

    type LoggingLevel =
      | Info
      | Debug
      | Trace // Note the order here! 🤔
    
    Trace > Info // true
    
    let levels = [ Trace; Debug; Info; Trace; Debug; Info ]
    
    levels |> List.sort
    // [Info; Info; Debug; Debug; Trace; Trace]
    // Comparison ✔
    
    levels |> List.countBy id
    // [(Trace, 2); (Debug, 2); (Info, 2)]
    // Equality ✔
    

    More info: https://stackoverflow.com/a/52220335/1256041