2

Let's assume I'm constructing an F# generic function f with a single argument integral, and this argument by the function semantics should be constrained to any .NET integral type from System.SByte through System.Int32 to System.Numerics.BigInteger.

One approach would be implementing

let inline f (integral: 'a) =
    (* function body here *) ...

and rely on compiler-deduced constraints to 'a derived from the actual contents of f's body, which may or may not coincide with the set of integral .NET types.

Another approach might be to force explicit hand-picked a priori constraints to 'a that would really guarantee for known .NET types that only integral types pass the static check, for example

let inline f (integral: ^a when ^a:(static member (|||): ^a * ^a-> ^a)) =
    (* function body here, unit for illustration *)()

or

let inline f< ^a when ^a : (static member (|||): ^a * ^a -> ^a)> (integral: ^a) =
    (* function body here, unit for illustration *)()

so f 1uy, f 1L, f 1I pass the static type check right away, but f 'a', f 1.0, f 1m do not.

What would be benefits, if any, of using second approach over the first?

Are there more idiomatic ways to reach the initial goal?

UPDATE 02/03/2014 Ironically, only today after looking at this answer managed to get a working code out of @kvb's prompt:

let inline implementation integral = ((* whatever implementation here *)) 

type Integral = Integral with 
    static member ($) (Integral, value: byte) = implementation value
    static member ($) (Integral, value: sbyte) = implementation value
    static member ($) (Integral, value: int16) = implementation value
    static member ($) (Integral, value: uint16) = implementation value
    static member ($) (Integral, value: int) = implementation value
    static member ($) (Integral, value: uint32) = implementation value
    static member ($) (Integral, value: int64) = implementation value
    static member ($) (Integral, value: uint64) = implementation value
    static member ($) (Integral, value: bigint) = implementation value

let inline doit integral = Integral $ integral

doit 1
doit 1I
doit 1.0 // does not compile
doit 1.0m // does not compile
doit '1' // does not compile
Community
  • 1
  • 1
Gene Belitski
  • 10,270
  • 1
  • 34
  • 54
  • 2
    Won't the answer depend on why you want to limit the allowed types in the first place? – kvb May 24 '13 at 15:09
  • An intent example, maybe a bit artificial: comparison behaves similarly to `sign` for integrals, but differently for `string`. – Gene Belitski May 24 '13 at 16:11
  • Hackish approach - add `GenericZero` to the argument which should limit you to Numeric types. You could then do a runtime check if `3/2` in that type was equal to `1` which won't happen for floating types. – John Palmer May 25 '13 at 06:33
  • https://code.google.com/p/fsharp-typeclasses/source/browse/Prelude.fs#37 – Mauricio Scheffer May 25 '13 at 15:41

2 Answers2

1

The first is better. F#'s type inference actually does constraints right. Explicit constraints are a way of stating what you want to do. What could be better than doing it and having the compiler statically prove that the argument is valid for the operations? I'm not sure I see a benefit of spelling out an "integral constraint" beyond what the body of the function itself dictates. But maybe you can lay out more of your case.

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • A bit of exaggeration, but imagine you define `let inline equals a b = match Operators.compare a b with | 1 | -1 -> false | _ -> true` and then get one unfortunate day that `equals "A" "b"` is `true`. – Gene Belitski May 24 '13 at 16:06
  • Are you shadowing built-in functions? Regardless, it doesn't seem like your problem is best solved with constraints, but a fuller example of what you're trying to do would help. – Daniel May 24 '13 at 16:12
  • @GeneBelitski Well, that would be a bug in your code that you should fix. I'm not sure how is restricting to types that also support bitwise or going to help. Especially since for example [`int.CompareTo()` is documented](http://msdn.microsoft.com/en-us/library/y2ky8xsk.aspx) to return a negative number when the result is “less”, not necessarily -1. – svick May 24 '13 at 18:21
  • @svick: I reiterate that this is a completely artificial, made-up, "bad practice", comparison implementation-dependent example whatsoever; nevertheless the fact is that coincidentally with compiler-deduced constrains the observed function behavior would be wrong, and with hand-picked constraining arguments to only integral types - right. – Gene Belitski May 24 '13 at 19:15
1

If you really just want to limit the possibilities to exactly the integral types, then using overloaded static methods would be one alternative. To reduce code duplication, these can all be implemented in terms of an unrestricted generic function.

kvb
  • 54,864
  • 2
  • 91
  • 133