Idiomatic path in F # to match an interface to a type rather than an instance level

What is the most idiomatic way in F # to deal with the following. Suppose I have a property that I want the type to satisfy, which does not make sense at the instance level, but ideally I would like to have access to the template against it?

To make this more specific, I defined an interface representing the concept of a ring (in the sense of abstract algebra). I would go for:

one.

// 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 suppose I use it as follows:

 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 allows me to make good use of the unit tests that I wrote to test the properties of the ring. However, I cannot match this correspondence.

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 I can now match the image, but that also means that I can write nonsense such as

 let ring = (Int 3) :> IRing2<_> 

3.

I could use an extra level of indirection and basically define

 type IConvertibleToRing<'a when 'a: equality> abstract member UnderlyingTypeAsRing : IRing3<'a> with get 

and then basically build IRing3 <_> in the same way as in C # 1. This will let me write:

 let ring = ((Int 3) :> IConvertibleToRing).UnderlyingTypeAsRing 

which is detailed, but at least what I write is no longer read as nonsense. However, along with verbosity, an additional level of complexity does not actually โ€œfeelโ€ justice here.

four.

I hadnโ€™t fully thought about it yet, but I could just have an Integer type without implementing any interfaces, and then a module named Integer, allowing the associated values โ€‹โ€‹for the Ring interfaces. I believe that then I could 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 the module suffix in it with a compiled name)? This will combine the advantages of # 1 and # 2, I think, but I'm not sure if this is possible and / or too far-fetched?

Just for the background: just for this, I'm trying to implement my own mini algebraic algebra system (like Mathematica or Maple) in F #, and I decided that I came across enough algebraic structures to start introducing interfaces like IRing for unit testing , as well as (potentially) later for working with general operations on such algebraic structures.

I understand that part of what is here or not possible has more to do with the limitations on how this can be done in .NET and not in F #. If my intention is clear enough, I will be interested to know here how other functional languages โ€‹โ€‹work around these kinds of design issues.

+1
f #
Jul 27 '14 at 3:56
source share
1 answer

As for your question about how you can implement Rings in other functional languages, in Haskell you usually define a Type Class Ring with all Ring operations.

There are no type classes in F #, however you can get closer with inline and overload:

 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 extend this approach, you can look at FsControl , which already defines Monoid with zero (Mempty) and Add (Mappend). You can send a pull request for Ring.

Now, to be practical, if you plan to use all this with numbers only, why not use GenericNumbers in F #, (+) and (*) are already common, then you have LanguagePrimitives.GenericZero and LanguagePrimitives.GenericOne .

+1
Jul 27 '14 at 7:30
source share



All Articles