How do I properly override a constrained method
Asked Answered
N

2

5

How do I override the method Zero in the following code in such a way that I can return Euro(0) for the definiton in the type Euro

[<AbstractClass>] 
type Currency () =
    abstract member Zero<'T when 'T :> Currency > : unit -> 'T

type Euro (value: int) =
    inherit Currency()
    member this.Value = value
    override this.Zero() = Euro(0) :> _
Niggard answered 15/2, 2017 at 15:50 Comment(3)
Zero is a generic method and needs to return a value for any subtype of Currency the caller chooses so you can't fix it within your Euro type. At the moment a caller could do let c = new Euro(0) :> Currency; c.Zero<Dollar>(). Can you move T to the Currency class?Lough
Isnt this for all practical purpose a DU? No?Pulling
@HelgeReneUrholm: No, in this case there's an open universe of types. You lose exhaustive matches, but can add new subtypes without recompiling (or in other files/assemblies, etc...)Sermonize
Z
7

Have you tried lifting the generic constraint to the class level?

[<AbstractClass>] 
type Currency<'T when 'T :> Currency<'T>>() =
    abstract member Zero : unit -> 'T

type Euro (value: int) =
    inherit Currency<Euro>()
    member this.Value = value
    override this.Zero() = Euro(0)

Though self-referencing generics always seems weird to me, this is how it'd be done in, for example, C#.

Zealous answered 15/2, 2017 at 16:6 Comment(3)
Oh my goodness. I am fainting! Thanks thou. PS: I have another solution too.Niggard
Coolio... what's the alternative solution? Make the member static and forgo the inheritance?Zealous
@Zealous fwiw, I've added another solution.Breunig
B
1

There is also the 'roll your own typeclass' technique in F#. Basically your abstract type's (instance and static) members become the fields of a 'typeclass' record, and values of that record are typeclass instances. You can have a 'euro' instance, a 'dollar' instance, and so on:

module Currency =
  type t<[<Measure>] 'a> =
    { zero : decimal<'a>; from : decimal -> decimal<'a> }

  /// Helper function to easily create typeclass instances for any
  /// currency.
  let make<[<Measure>] 'a> (curr_unit : decimal<'a>) : t<'a> =
    { zero = curr_unit - curr_unit; from = ((*) curr_unit) }

  [<Measure>] type euro
  let euro : t<euro> = make 1m<euro>

  [<Measure>] type dollar
  let dollar : t<dollar> = make 1m<dollar>

The unique thing about F# is that the type parameter that is passed to each typeclass instance can actually be a measure type, which is appropriate for currencies.

Breunig answered 16/2, 2017 at 5:21 Comment(5)
This OK as long as you dont want to put those different types into a generic listNiggard
@robkuz: I would have gone with units-of-measure in the first place, but your application requires to put different currencies into one list, is that correct?Lycia
@AntonSchwaighofer yupNiggard
@Niggard I may be confused here, but I haven't been able to put them in a generic list with Paulmichael's technique either. If the currencies are lifted to the type, you can't put them in the same list, no matter what specific technique you use.Breunig
nope you can't. Something I realized only later thou :-(Niggard

© 2022 - 2024 — McMap. All rights reserved.