问题
What's the most idiomatic way in F# to deal with the following. Suppose I have a property I want a type to satisfy that doesn't make sense on an instance level, but ideally I would like to have some pattern matching available against it?
To make this more concrete, I have defined an interface representing the concept of a ring (in the abstract algebra sense). Would I go for:
1.
// Misses a few required operations for now
type IRing1<'a when 'a: equality> =
abstract member Zero: 'a with get
abstract member Addition: ('a*'a -> 'a) with get
and let's assume I'm using it like this:
type Integer =
| Int of System.Numerics.BigInteger
static member Zero with get() = Int 0I
static member (+) (Int a, Int b) = Int (a+b)
static member AsRing
with get() =
{ new IRing1<_> with
member __.Zero = Integer.Zero
member __.Addition = Integer.(+) }
which allows me to write things like:
let ring = Integer.AsRing
which then lets me to nicely use the unit tests I've written for verifying the properties of a ring. However, I can't pattern match on this.
2.
type IRing2<'a when 'a: equality> =
abstract member Zero: 'a with get
abstract member Addition: ('a*'a -> 'a) with get
type Integer =
| Int of System.Numerics.BigInteger
static member Zero with get() = Int 0I
static member (+) (Int a, Int b) = Int (a+b)
interface IRing2<Integer> with
member __.Zero = Integer.Zero
member __.Addition with get() = Integer.(+)
which now I can pattern match, but it also means that I can write nonsense such as
let ring = (Int 3) :> IRing2<_>
3.
I could use an additional level of indirection and basically define
type IConvertibleToRing<'a when 'a: equality>
abstract member UnderlyingTypeAsRing : IRing3<'a> with get
and then basically construct the IRing3<_> in the same way as under #1. This would let me write:
let ring = ((Int 3) :> IConvertibleToRing).UnderlyingTypeAsRing
which is verbose but at least what I'm writing doesn't read as nonsense anymore. However, next to the verbosity, the additional level of complexity gained doesn't really "feel" justifiable here.
4.
I haven't fully thought this one through yet, but I could just have an Integer type without implementing any interfaces and then a module named Integer, having let bound values for the Ring interfaces. I suppose I could then use reflection in a helper function that creates any IRing implementation for any type where there is also a module with the same name (but with a module suffix in it's compiled name) available? This would combine the benefits of #1 and #2 I guess, but I'm not sure whether it's possible and/or too contrived?
Just for background: Just for the heck of it, I'm trying to implement my own mini Computer Algebra System (like e.g. Mathematica or Maple) in F# and I figured that I would come across enough algebraic structures to start introducing interfaces such as IRing for unit testing as well as (potentially) later for dealing with general operations on such algebraic structures.
I realize part of what is or isn't possible here has more to do with restrictions on how things can be done in .NET rather than F#. If my intention is clear enough, I'd be curious to here in comments how other functional languages work around this kind of design questions.
回答1:
Regarding your question about how can you implement Rings in other functional languages, in Haskell you will typically define a Type Class Ring with all Ring operations.
In F# there are no Type Classes, however you can get closer using inline and overloading:
module Ring =
type Zero = Zero with
static member ($) (Zero, a:int) = 0
static member ($) (Zero, a:bigint) = 0I
// more overloads
type Add = Add with
static member ($) (Add, a:int ) = fun (b:int ) -> a + b
static member ($) (Add, a:bigint) = fun (b:bigint) -> a + b
// more overloads
type Multiply = Multiply with
static member ($) (Multiply, a:int ) = fun (b:int ) -> a * b
static member ($) (Multiply, a:bigint) = fun (b:bigint) -> a * b
// more overloads
let inline zero() :'t = Zero $ Unchecked.defaultof<'t>
let inline (<+>) (a:'t) (b:'t) :'t= (Add $ a) b
let inline (<*>) (a:'t) (b:'t) :'t= (Multiply $ a) b
// Usage
open Ring
let z : int = zero()
let z': bigint = zero()
let s = 1 <+> 2
let s' = 1I <+> 2I
let m = 2 <*> 3
let m' = 2I <*> 3I
type MyCustomNumber = CN of int with
static member ($) (Ring.Zero, a:MyCustomNumber) = CN 0
static member ($) (Ring.Add, (CN a)) = fun (CN b) -> CN (a + b)
static member ($) (Ring.Multiply, (CN a)) = fun (CN b) -> CN (a * b)
let z'' : MyCustomNumber = zero()
let s'' = CN 1 <+> CN 2
If you want to scale up with this approach you can have a look at FsControl which already defines Monoid with Zero (Mempty) and Add (Mappend). You can submit a pull request for Ring.
Now to be practical if you are planning to use all this only with numbers why not use GenericNumbers in F#, (+)
and (*)
are already generic then you have LanguagePrimitives.GenericZero
and LanguagePrimitives.GenericOne
.
来源:https://stackoverflow.com/questions/24977767/idiomatic-way-in-f-to-establish-adherence-to-an-interface-on-type-rather-than-i