Given a generic interface declaration like
type IFoo<'T,'MT> =
abstract Return : 'T -> 'MT
abstract Bind : 'MT * ('T -> 'MT) -> 'MT
it's actually possible to use object expressions as computation builder expressions, which could provide for an interesting approach to the division between encapsulation and execution logic of monadic workflows.
let inline addOption mx my =
{ new IFoo<_,_> with
member __.Return x = Some x
member __.Bind(ma, f) = Option.bind f ma }
{ let! x = mx
let! y = my
return x + y }
// val inline addOption :
// mx: ^a option -> my: ^a option -> ^a option
// when ^a : (static member ( + ) : ^a * ^a -> ^a)
addOption (Some 1) (Some 2)
// val it : int option = Some 3
addOption None (Some 2)
// val it : int option = None
The compiler checks on the type of the expression if the expected methods are present. But it is only halfway there; because for real monads, I would need to get the method signature abstract Bind : 'MT * ('T -> 'MU) -> 'MU honoured, a projection to a different non-encapsulated type. Why can't this be done?
type Mul = Mul with member inline __.Op(a: ^a,b: ^a) = a*b
type Div = Div with member inline __.Op(a: ^a,b: ^a) = a/b
type Add = Add with member inline __.Op(a: ^a,b: ^a) = a+b
type Sub = Sub with member inline __.Op(a: ^a,b: ^a) = a-b
let inline op x a b =
(^a: (member Op: ^b * ^b -> ^b) x,a,b)
let inline tup2 a b c d = op Mul a b, op Mul c d
let inline tup2' f a b c d = op f a b, op f c d
let a = tup2 1 2 3.0f 4.0f
//let b = tup2' Mul 1 2 3.0f 4.0f //Gives a type error.
I am wondering if there is a way to make the types do what I want in the example above or if I have finally reached the limitation of F#'s type system. Actually, there is a way to make the above work and that is to put all the types into one DU and then pattern match on the DU type like the following:
type Operation =
| Mul
| Add
| Sub
| Div
member inline t.Op a b =
match t with
| Mul -> a * b
| Add -> a + b
| Sub -> a - b
| Div -> a / b
let inline map' (f: Operation) a b c d =
(f.Op a b, f.Op c d)
map' Mul 1 2 3.0f 4.0f
But assuming the first example worked, it would be a more dynamic solution. It is a pity something like passing a higher order function by name inside an argument and having it inlined on the spot to make it generic is not possible.
This limitation of most modern type systems is well explained in kvb's answer to this question.
Here's a workaround, based on the hack suggested there. In fact it's very similar to your code but less verbose.
type Mul = Mul with static member inline ($) (Mul, a: ^a) = fun (b: ^a) -> a*b
type Div = Div with static member inline ($) (Div, a: ^a) = fun (b: ^a) -> a/b
type Add = Add with static member inline ($) (Add, a: ^a) = fun (b: ^a) -> a+b
type Sub = Sub with static member inline ($) (Sub, a: ^a) = fun (b: ^a) -> a-b
let inline tup2' f a b c d = (f $ a) b, (f $ c) d
let b = tup2' Mul 1 2 3.0f 4.0f
The idea is that instead of defining a function you define a type with a single method (which you already did) in this case it will be an operator which would mean apply.
So instead of doing f x you will write f $ x.
UPDATE
As said before, your code is not far from the solution suggested in that answer.
Here's a working example which is even closer to your original code:
type Mul = Mul with static member inline Op(Mul, a: ^a,b: ^a) = a*b
type Div = Div with static member inline Op(Div, a: ^a,b: ^a) = a/b
type Add = Add with static member inline Op(Add, a: ^a,b: ^a) = a+b
type Sub = Sub with static member inline Op(Sub, a: ^a,b: ^a) = a-b
let inline op x a b = ((^a or ^b): (static member Op: ^a * ^b * ^b -> ^b) (x, a, b))
let inline tup2 a b c d = op Mul a b, op Mul c d
let inline tup2' f a b c d = op f a b, op f c d
let a = tup2 1 2 3.0f 4.0f
let b = tup2' Mul 1 2 3.0f 4.0f //Gives NO type error.
So that's basically your original code but using static methods and using an or in the constraints. By doing this the compiler doesn't solve the constraint early and so it works.
I used the operator because it's less verbose and in this case I like how it reads, since Haskell $ means function application.
when use
let add a b = a + b
it works for int. and
let add2 a b:float = a + b
work for float.
Is it possible to write a function work for int and long (float, byte .etc) in F#?
i need a function whose "a" work for int and long like this:
let f a b = a >>> b
let f (a:int64) b = a >>> b
is it possible in F#?
When you want to write a function that can work with multiple different numeric types, you need to use inline and "static member constraints". The problem is that normal .NET generics cannot capture constraints like "supports the + operator".
To make your sample add function generic, you can use inline:
let inline add x y = x + y
When you look at the inferred type, you'll see something like this:
val inline add :
x: ^a -> y: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
This essentially says that the function can be called on any two arguments that support the + operator. Note that this only works because the function is inline and so the F# compiler does not have to rely on .NET to express the constraints. The same works for the >>> operator:
let inline f a b = a >>> b
The function can now be used with both int64 and int arguments:
f 1024L 2
f 1024 2
I wrote an article about writing generic numeric code in F# some time ago which you may find useful too.
I'm quite new to F# and find type inference really is a cool thing. But currently it seems that it also may lead to code duplication, which is not a cool thing. I want to sum the digits of a number like this:
let rec crossfoot n =
if n = 0 then 0
else n % 10 + crossfoot (n / 10)
crossfoot 123
This correctly prints 6. But now my input number does not fit int 32 bits, so I have to transform it to.
let rec crossfoot n =
if n = 0L then 0L
else n % 10L + crossfoot (n / 10L)
crossfoot 123L
Then, a BigInteger comes my way and guess what…
Of course, I could only have the bigint version and cast input parameters up and output parameters down as needed. But first I assume using BigInteger over int has some performance penalities. Second let cf = int (crossfoot (bigint 123)) does just not read nice.
Isn't there a generic way to write this?
Building on Brian's and Stephen's answers, here's some complete code:
module NumericLiteralG =
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
let inline FromInt32 (n:int) =
let one : ^a = FromOne()
let zero : ^a = FromZero()
let n_incr = if n > 0 then 1 else -1
let g_incr = if n > 0 then one else (zero - one)
let rec loop i g =
if i = n then g
else loop (i + n_incr) (g + g_incr)
loop 0 zero
let inline crossfoot (n:^a) : ^a =
let (zero:^a) = 0G
let (ten:^a) = 10G
let rec compute (n:^a) =
if n = zero then zero
else ((n % ten):^a) + compute (n / ten)
compute n
crossfoot 123
crossfoot 123I
crossfoot 123L
UPDATE: Simple Answer
Here's a standalone implementation, without the NumericLiteralG module, and a slightly less restrictive inferred type:
let inline crossfoot (n:^a) : ^a =
let zero:^a = LanguagePrimitives.GenericZero
let ten:^a = (Seq.init 10 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum
let rec compute (n:^a) =
if n = zero then zero
else ((n % ten):^a) + compute (n / ten)
compute n
Explanation
There are effectively two types of generics in F#: 1) run-type polymorphism, via .NET interfaces/inheritance, and 2) compile time generics. Compile-time generics are needed to accommodate things like generic numerical operations and something like duck-typing (explicit member constraints). These features are integral to F# but unsupported in .NET, so therefore have to be handled by F# at compile time.
The caret (^) is used to differentiate statically resolved (compile-time) type parameters from ordinary ones (which use an apostrophe). In short, 'a is handled at run-time, ^a at compile-time–which is why the function must be marked inline.
I had never tried to write something like this before. It turned out clumsier than I expected. The biggest hurdle I see to writing generic numeric code in F# is: creating an instance of a generic number other than zero or one. See the implementation of FromInt32 in this answer to see what I mean. GenericZero and GenericOne are built-in, and they're implemented using techniques that aren't available in user code. In this function, since we only needed a small number (10), I created a sequence of 10 GenericOnes and summed them.
I can't explain as well why all the type annotations are needed, except to say that it appears each time the compiler encounters an operation on a generic type it seems to think it's dealing with a new type. So it ends up inferring some bizarre type with duplicated resitrictions (e.g. it may require (+) multiple times). Adding the type annotations lets it know we're dealing with the same type throughout. The code works fine without them, but adding them simplifies the inferred signature.
In addition to kvb's technique using Numeric Literals (Brian's link), I've had a lot of success using a different technique which can yield better inferred structural type signatures and may also be used to create precomputed type-specific functions for better performance as well as control over supported numeric types (since you will often want to support all integral types, but not rational types, for example): F# Static Member Type Constraints.
Following up on the discussion Daniel and I have been having about the inferred type signatures yielded by the different techniques, here is an overview:
NumericLiteralG Technique
module NumericLiteralG =
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
let inline FromInt32 (n:int) =
let one = FromOne()
let zero = FromZero()
let n_incr = if n > 0 then 1 else -1
let g_incr = if n > 0 then one else (zero - one)
let rec loop i g =
if i = n then g
else loop (i + n_incr) (g + g_incr)
loop 0 zero
Crossfoot without adding any type annotations:
let inline crossfoot1 n =
let rec compute n =
if n = 0G then 0G
else n % 10G + compute (n / 10G)
compute n
val inline crossfoot1 :
^a -> ^e
when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^d) and
^a : (static member get_Zero : -> ^a) and
( ^a or ^f) : (static member ( / ) : ^a * ^f -> ^a) and
^a : equality and ^b : (static member get_Zero : -> ^b) and
( ^b or ^c) : (static member ( - ) : ^b * ^c -> ^c) and
( ^b or ^c) : (static member ( + ) : ^b * ^c -> ^b) and
^c : (static member get_One : -> ^c) and
( ^d or ^e) : (static member ( + ) : ^d * ^e -> ^e) and
^e : (static member get_Zero : -> ^e) and
^f : (static member get_Zero : -> ^f) and
( ^f or ^g) : (static member ( - ) : ^f * ^g -> ^g) and
( ^f or ^g) : (static member ( + ) : ^f * ^g -> ^f) and
^g : (static member get_One : -> ^g)
Crossfoot adding some type annotations:
let inline crossfoot2 (n:^a) : ^a =
let (zero:^a) = 0G
let (ten:^a) = 10G
let rec compute (n:^a) =
if n = zero then zero
else ((n % ten):^a) + compute (n / ten)
compute n
val inline crossfoot2 :
^a -> ^a
when ^a : (static member get_Zero : -> ^a) and
( ^a or ^a0) : (static member ( - ) : ^a * ^a0 -> ^a0) and
( ^a or ^a0) : (static member ( + ) : ^a * ^a0 -> ^a) and
^a : equality and ^a : (static member ( + ) : ^a * ^a -> ^a) and
^a : (static member ( % ) : ^a * ^a -> ^a) and
^a : (static member ( / ) : ^a * ^a -> ^a) and
^a0 : (static member get_One : -> ^a0)
Record Type Technique
module LP =
let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a>
let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a>
let inline two_of (target:'a) : 'a = one_of(target) + one_of(target)
let inline three_of (target:'a) : 'a = two_of(target) + one_of(target)
let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target)
let inline any_of (target:'a) (x:int) : 'a =
let one:'a = one_of target
let zero:'a = zero_of target
let xu = if x > 0 then 1 else -1
let gu:'a = if x > 0 then one else zero-one
let rec get i g =
if i = x then g
else get (i+xu) (g+gu)
get 0 zero
type G<'a> = {
negone:'a
zero:'a
one:'a
two:'a
three:'a
any: int -> 'a
}
let inline G_of (target:'a) : (G<'a>) = {
zero = zero_of target
one = one_of target
two = two_of target
three = three_of target
negone = negone_of target
any = any_of target
}
open LP
Crossfoot, no annotations required for nice inferred signature:
let inline crossfoot3 n =
let g = G_of n
let ten = g.any 10
let rec compute n =
if n = g.zero then g.zero
else n % ten + compute (n / ten)
compute n
val inline crossfoot3 :
^a -> ^a
when ^a : (static member ( % ) : ^a * ^a -> ^b) and
( ^b or ^a) : (static member ( + ) : ^b * ^a -> ^a) and
^a : (static member get_Zero : -> ^a) and
^a : (static member get_One : -> ^a) and
^a : (static member ( + ) : ^a * ^a -> ^a) and
^a : (static member ( - ) : ^a * ^a -> ^a) and ^a : equality and
^a : (static member ( / ) : ^a * ^a -> ^a)
Crossfoot, no annotations, accepts precomputed instances of G:
let inline crossfootG g ten n =
let rec compute n =
if n = g.zero then g.zero
else n % ten + compute (n / ten)
compute n
val inline crossfootG :
G< ^a> -> ^b -> ^a -> ^a
when ( ^a or ^b) : (static member ( % ) : ^a * ^b -> ^c) and
( ^c or ^a) : (static member ( + ) : ^c * ^a -> ^a) and
( ^a or ^b) : (static member ( / ) : ^a * ^b -> ^a) and
^a : equality
I use the above in practice since then I can make precomputed type specific versions which don't suffer from the performance cost of Generic LanguagePrimitives:
let gn = G_of 1 //int32
let gL = G_of 1L //int64
let gI = G_of 1I //bigint
let gD = G_of 1.0 //double
let gS = G_of 1.0f //single
let gM = G_of 1.0m //decimal
let crossfootn = crossfootG gn (gn.any 10)
let crossfootL = crossfootG gL (gL.any 10)
let crossfootI = crossfootG gI (gI.any 10)
let crossfootD = crossfootG gD (gD.any 10)
let crossfootS = crossfootG gS (gS.any 10)
let crossfootM = crossfootG gM (gM.any 10)
Since the question of how to make the type signatures less hairy when using the generalized numeric literals has come up, I thought I'd put in my two cents. The main issue is that F#'s operators can be asymmetric so that you can do stuff like System.DateTime.Now + System.TimeSpan.FromHours(1.0), which means that F#'s type inference adds intermediary type variables whenever arithmetic operations are being performed.
In the case of numerical algorithms, this potential asymmetry isn't typically useful and the resulting explosion in the type signatures is quite ugly (although it generally doesn't affect F#'s ability to apply the functions correctly when given concrete arguments). One potential solution to this problem is to restrict the types of the arithmetic operators within the scope that you care about. For instance, if you define this module:
module SymmetricOps =
let inline (+) (x:'a) (y:'a) : 'a = x + y
let inline (-) (x:'a) (y:'a) : 'a = x - y
let inline (*) (x:'a) (y:'a) : 'a = x * y
let inline (/) (x:'a) (y:'a) : 'a = x / y
let inline (%) (x:'a) (y:'a) : 'a = x % y
...
then you can just open the SymmetricOps module whenever you want have the operators apply only to two arguments of the same type. So now we can define:
module NumericLiteralG =
open SymmetricOps
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
let inline FromInt32 (n:int) =
let one = FromOne()
let zero = FromZero()
let n_incr = if n > 0 then 1 else -1
let g_incr = if n > 0 then one else (zero - one)
let rec loop i g =
if i = n then g
else loop (i + n_incr) (g + g_incr)
loop 0 zero
and
open SymmetricOps
let inline crossfoot x =
let rec compute n =
if n = 0G then 0G
else n % 10G + compute (n / 10G)
compute x
and the inferred type is the relatively clean
val inline crossfoot :
^a -> ^a
when ^a : (static member ( - ) : ^a * ^a -> ^a) and
^a : (static member get_One : -> ^a) and
^a : (static member ( % ) : ^a * ^a -> ^a) and
^a : (static member get_Zero : -> ^a) and
^a : (static member ( + ) : ^a * ^a -> ^a) and
^a : (static member ( / ) : ^a * ^a -> ^a) and ^a : equality
while we still get the benefit of a nice, readable definition for crossfoot.
I stumbled upon this topic when I was looking for a solution and I am posting my answer, because I found a way to express generic numeral without the less than optimal implementation of building up the number by hand.
open System.Numerics
// optional
open MathNet.Numerics
module NumericLiteralG =
type GenericNumber = GenericNumber with
static member instance (GenericNumber, x:int32, _:int8) = fun () -> int8 x
static member instance (GenericNumber, x:int32, _:uint8) = fun () -> uint8 x
static member instance (GenericNumber, x:int32, _:int16) = fun () -> int16 x
static member instance (GenericNumber, x:int32, _:uint16) = fun () -> uint16 x
static member instance (GenericNumber, x:int32, _:int32) = fun () -> x
static member instance (GenericNumber, x:int32, _:uint32) = fun () -> uint32 x
static member instance (GenericNumber, x:int32, _:int64) = fun () -> int64 x
static member instance (GenericNumber, x:int32, _:uint64) = fun () -> uint64 x
static member instance (GenericNumber, x:int32, _:float32) = fun () -> float32 x
static member instance (GenericNumber, x:int32, _:float) = fun () -> float x
static member instance (GenericNumber, x:int32, _:bigint) = fun () -> bigint x
static member instance (GenericNumber, x:int32, _:decimal) = fun () -> decimal x
static member instance (GenericNumber, x:int32, _:Complex) = fun () -> Complex.op_Implicit x
static member instance (GenericNumber, x:int64, _:int64) = fun () -> int64 x
static member instance (GenericNumber, x:int64, _:uint64) = fun () -> uint64 x
static member instance (GenericNumber, x:int64, _:float32) = fun () -> float32 x
static member instance (GenericNumber, x:int64, _:float) = fun () -> float x
static member instance (GenericNumber, x:int64, _:bigint) = fun () -> bigint x
static member instance (GenericNumber, x:int64, _:decimal) = fun () -> decimal x
static member instance (GenericNumber, x:int64, _:Complex) = fun () -> Complex.op_Implicit x
static member instance (GenericNumber, x:string, _:float32) = fun () -> float32 x
static member instance (GenericNumber, x:string, _:float) = fun () -> float x
static member instance (GenericNumber, x:string, _:bigint) = fun () -> bigint.Parse x
static member instance (GenericNumber, x:string, _:decimal) = fun () -> decimal x
static member instance (GenericNumber, x:string, _:Complex) = fun () -> Complex(float x, 0.0)
// MathNet.Numerics
static member instance (GenericNumber, x:int32, _:Complex32) = fun () -> Complex32.op_Implicit x
static member instance (GenericNumber, x:int32, _:bignum) = fun () -> bignum.FromInt x
static member instance (GenericNumber, x:int64, _:Complex32) = fun () -> Complex32.op_Implicit x
static member instance (GenericNumber, x:int64, _:bignum) = fun () -> bignum.FromBigInt (bigint x)
static member instance (GenericNumber, x:string, _:Complex32) = fun () -> Complex32(float32 x, 0.0f)
static member instance (GenericNumber, x:string, _:bignum) = fun () -> bignum.FromBigInt (bigint.Parse x)
let inline genericNumber num = Inline.instance (GenericNumber, num) ()
let inline FromZero () = LanguagePrimitives.GenericZero
let inline FromOne () = LanguagePrimitives.GenericOne
let inline FromInt32 n = genericNumber n
let inline FromInt64 n = genericNumber n
let inline FromString n = genericNumber n
this implementation comes by without complicated iteration during the cast. It uses FsControl for the Instance module.
http://www.fssnip.net/mv
Is crossfoot exactly what you want to do, or is it just summing the digits of a long number?
because if you just want to sum the digits, then:
let crossfoot (x:'a) = x.ToString().ToCharArray()
|> (Array.fold(fun acc x' -> if x' <> '.'
then acc + (int x')
else acc) 0)
... And you are done.
Anyways,
Can you convert stuff to a string, drop the decimal point, remember where the decimal point is, interpret it as an int, run crossfoot?
Here is my solution. I am not sure exactly how you want "crossfoot" to work when you have a decimal point added.
For instance, do you want: crossfoot(123.1) = 7 or crossfoot(123.1) = 6.1? (I'm assuming you want the latter)
Anyways, the code does allow you to work with numbers as generics.
let crossfoot (n:'a) = // Completely generic input
let rec crossfoot' (a:int) = // Standard integer crossfoot
if a = 0 then 0
else a%10 + crossfoot' (a / 10)
let nstr = n.ToString()
let nn = nstr.Split([|'.'|]) // Assuming your main constraint is float/int
let n',n_ = if nn.Length > 1 then nn.[0],nn.[1]
else nn.[0],"0"
let n'',n_' = crossfoot'(int n'),crossfoot'(int n_)
match n_' with
| 0 -> string n''
| _ -> (string n'')+"."+(string n_')
If you need to input big integers or int64 stuff, the way crossfoot works, you can just split the big number into bitesize chunks (strings) and feed them into this function, and add them together.
In my quest to learn more F#, I tried to implement an "accumulator generator" as described by Paul Graham here. My best solution so far is completely dynamically typed:
open System
let acc (init:obj) : obj->obj=
let state = ref init
fun (x:obj) ->
if (!state).GetType() = typeof<Int32>
&& x.GetType() = typeof<Int32> then
state := (Convert.ToInt32(!state) + Convert.ToInt32(x)) :> obj
else
state := (Convert.ToDouble(!state) + Convert.ToDouble(x)) :> obj
!state
do
let x : obj -> obj = acc 1 // the type annotation is necessary here
(x 5) |> ignore
printfn "%A" (x 2) // prints "8"
printfn "%A" (x 2.3) // prints "10.3"
I have three questions:
If I remove the type annotation for x, the code fails to compile because the compiler infers type int -> obj for x - although acc is annotated to return an obj->obj. Why is that and can I avoid it?
Any ideas to improve this dynamically typed version?
Is it possible to implement this with proper static types? Maybe with member constraints? (It is possible in Haskell, but not in OCaml, AFAIK)
In my quest to learn more F#, I tried to implement an "accumulator generator" as described by Paul Graham here.
This problem requires the existence of an unspecified numeric tower. Lisp happens to have one and it happens to be adequate for Paul Graham's examples because this problem was specifically designed to make Lisp look artificially good.
You can implement a numeric tower in F# either using a union type (like type number = Int of int | Float of float) or by boxing everything. The following solution uses the latter approach:
let add (x: obj) (y: obj) =
match x, y with
| (:? int as m), (:? int as n) -> box(m+n)
| (:? int as n), (:? float as x)
| (:? float as x), (:? int as n) -> box(x + float n)
| (:? float as x), (:? float as y) -> box(x + y)
| _ -> failwith "Run-time type error"
let acc x =
let x = ref x
fun (y: obj) ->
x := add !x y
!x
let x : obj -> _ = acc(box 1)
do x(box 5)
do acc(box 3)
do printfn "%A" (x(box 2.3))
However, numeric towers are virtually useless in the real world. Unless you are very careful, trying to learn from these kinds of borked challenges will do you more harm than good. You should leave asking yourself why we do not want a numeric tower, do not want to box and do not want run-time type promotion?
Why didn't we just write:
let x = 1
let x = x + 5
ignore(3)
let x = float x + 2.3
We know the type of x at every step. Every number is stored unboxed. We know that this code will never produce a run-time type error...
I agree with Jon that this is quite artificial example and it is not a good starting point for learning F#. However, you can use static member constraints to get reasonably close without dynamic casts and reflection. If you mark it as inline and add convert both of the parameters using float:
let inline acc x =
let x = ref (float x)
fun y ->
x := (float y) + !x
!x
You'll get a function with the following type:
val inline acc :
^a -> ( ^b -> float)
when ^a : (static member op_Explicit : ^a -> float) and
^b : (static member op_Explicit : ^b -> float)
The function takes any two arguments that can be explicitly converted to float. The only limitation compared to the LISP version (I guess) is that it always returns float (as the most universal numeric type available). You can write something like:
> acc 1 2;; // For two integers, it returns float
val it : float = 3.0
> acc 1 2.1;; // integer + float
val it : float = 3.1
> acc 1 "31";; // It even works with strings!
val it : float = 32.0
It's definitely not possible to implement this with proper static types. You say you can in Haskell, but I don't believe you.
The problem with trying to do this with static typing is in adding two different numbers of possibly different types while preserving the type of the left-hand side. As Jon Harrop says this is possible with a union type. Once you've defined the union type and a corresponding addition operation which works as mentioned, the actual accumulator is very simple. My implementation:
module MyTest
type Numeric =
| NInt of int
| NFloat of float
member this.Add(other : Numeric) : Numeric =
match this with
| NInt x ->
match other with
| NInt y -> NInt (x + y)
| NFloat y -> NInt (x + (int y))
| NFloat x ->
match other with
| NInt y -> NFloat (x + (float y))
| NFloat y -> NFloat (x + y)
override this.ToString() =
match this with
| NInt x -> x.ToString()
| NFloat x -> x.ToString()
let foo (n : Numeric) =
let acc = ref n
fun i ->
acc := (!acc).Add(i)
!acc
let f = foo (NFloat 1.1)
(2 |> NInt |> f).ToString() |> printfn "%s"