13

I want to define a method shared by all members of a discriminated union. Currently I've implemented it like this, but it seems really inelegant- surely there is a better way. Suggestions?

type A = 
   {AData:string}
   member this.SharedMethod (x:float) : int= ...
type B =
   {BData:float}
   member this.SharedMethod (x:float) : int= ...
type AB =
| A of A
| B of B

let CallSharedMethod (ab:AB) x =
   match ab with
   | AB.A(a') -> a'.SharedMethod x
   | AB.B(b') -> b'.SharedMethod x
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Robert Sim
  • 1,428
  • 11
  • 22
  • You are missing the instance binding, i.e.: this.SharedMethod otherwise it will not work. Also you need to add more information about what does your shared method with the instance of the DU (in fact is a record type), because if it does nothing you can use a regular function. I'm sure is not your case. – Gus Nov 25 '14 at 20:33
  • Smells to me like the method should be on the DU. But if not, you can use records to implement an interface. – Daniel Nov 25 '14 at 21:02
  • Yes, sorry my code was not exactly syntactically correct. I'm more interested in the pattern- how do I denote the fact that every subtype of the DU share a common method name? I'll edit the code to make it more correct. – Robert Sim Nov 25 '14 at 21:03
  • Ok, I can make CallSharedMethod a method of AB. But I still have to do the pattern match. Your other suggestion- making the record an interface seems like the best option. I'll try this. – Robert Sim Nov 25 '14 at 21:06
  • That will still require matching to get at the case fields. The only other option is reflection, or classes. – Daniel Nov 25 '14 at 21:20
  • 2
    If you want every case of a DU to act the same you're not discriminating. :) – Daniel Nov 25 '14 at 21:31

2 Answers2

20

What about something like this?

type AB =
    | A of string
    | B of float

    member self.SharedMethod (x : float) =
        match self with
        | A s -> x
        | B f -> f + x

This assumes that you want each variant of your sum type (aka discriminated union) to do something different with the float parameter.

For the case of A, I just return the original value since there's not much else I can do (since there is no generally useful relationship between string and float that yields a float).

Rodrick Chapman
  • 5,437
  • 2
  • 31
  • 32
9

In your example, you have augmented the record types A and B each with an instance member. However, you can not only augment record types, you can also augment union types (as shown in @Rodrick's answer). If you do this, the union's augmentation will be "shared" by each DU case, which is what you have asked for. To make this more explicit, I have renamed some parts of your example:

type A = { AData:string }
type B = { BData:float }

type AB =
    | ACase of A
    | BCase of B
    member __.SharedMethod x = 0

let callSharedMethod (ab:AB) x = ab.SharedMethod x

If you look at the compiled code in the object browser (or a decompiler like ILSpy), you will see that AB is compiled as the base class of two subclasses AB.ACase and AB.BCase, and SharedMethod belongs to the base class AB.

See also section 8.5.1 of the F# 3.0 specification: Members in Union Types.

Marc Sigrist
  • 3,964
  • 3
  • 22
  • 23