Records vs Single-Case Discriminated Unions - f#

What are the Pro's and Con's of using either
type Complex =
{
real: float;
imag: float;
}
or
type Complex =
Complex of
real: float *
imag: float
I am particularly interested in readability and handling in different situations.
And to a lesser extent, performance.

Using helper functions you could get the same out of both approaches.
Record
type ComplexRec =
{
real: float
imag: float
}
// Conciseness
let buildRec(r,i) =
{ real = r ; imag = i }
let c = buildRec(1.,5.)
// Built-in field acces
c.imag
Union type
type ComplexUnion =
Complex of
real: float * imag: float
// Built-in conciseness
let c = Complex(1.,5.)
// Get field - Could be implemented as members for a more OO feel
let getImag = function
Complex(_,i) -> i
getImag c
I imagine the (frequent) decomposition of the union type could influence performance, but I'm no expert on the subject.

In case of the record type, let's say that you declared symbol it : Complex you have immediate access to both fields like: it.real, it.imag
In the case of discriminated union (DU) you have to first unpack DU type like:
match it with
| Complex (real, imag) -> real, imag
DU makes sense when you have some choices on the type. Your Complex type doesn't branch to few cases, it only has one possible shape, case.
In this case I'm in favour of record type as it gives more readable code in usage.

Related

Types vs. Modules in F#

The answer on Confused about static dictionary in a type, in F# finished with one advice: and just in general: try to use fewer classes and more modules and functions; they're more idiomatic in F# and lead to fewer problems in general
Which is a great point, but my 30 years of OO just don't want to give up classes just yet (although I was fighting against C++ like crazy when we moved away from C...)
so let's take a practical real world object:
type Currency =
{
Ticker: string
Symbol: char
}
and MarginBracket =
{
MinSize: decimal
MaxSize: decimal
Leverage: int
InitialMargin: decimal
MaintenanceMargin: decimal
}
and Instrument =
{
Ticker: string
QuantityTickSize: int
PriceTickSize: int
BaseCurrency: Currency
QuoteCurrency: Currency
MinQuantity: decimal
MaxQuantity: decimal
MaxPriceMultiplier: decimal
MinPriceMultiplier: decimal
MarginBrackets: MarginBracket array
}
// formatting
static member private formatValueNoSign (precision: int) (value: decimal) =
let zeros = String.replicate precision "0"
String.Format($"{{0:#.%s{zeros}}}", value)
static member private formatValueSign (precision: int) (value: decimal) =
let zeros = String.replicate precision "0"
String.Format($"{{0:+#.%s{zeros};-#.%s{zeros}; 0.%s{zeros}}}", value)
member this.BaseSymbol = this.BaseCurrency.Symbol
member this.QuoteSymbol = this.QuoteCurrency.Symbol
member this.QuantityToString (quantity) = $"{this.BaseSymbol}{Instrument.formatValueSign this.QuantityTickSize quantity}"
member this.PriceToString (price) = $"{this.QuoteSymbol}{Instrument.formatValueNoSign this.PriceTickSize price}"
member this.SignedPriceToString (price) = $"{this.QuoteSymbol}{Instrument.formatValueSign this.PriceTickSize price}"
member this.RoundQuantity (quantity: decimal) = Math.Round (quantity, this.QuantityTickSize)
member this.RoundPrice (price : decimal) = Math.Round (price, this.PriceTickSize)
// price deviation allowed from instrument price
member this.LowAllowedPriceDeviation (basePrice: decimal) = this.MinPriceMultiplier * basePrice
member this.HighAllowedPriceDeviation (basePrice: decimal) = this.MaxPriceMultiplier * basePrice
module Instrument =
let private allInstruments = Dictionary<string, Instrument>()
let list () = allInstruments.Values
let register (instrument) = allInstruments.[instrument.Ticker] <- instrument
let exists (ticker: string) = allInstruments.ContainsKey (ticker.ToUpper())
let find (ticker: string) = allInstruments.[ticker.ToUpper()]
In this example, there is an Instrument object with its data and a few helper members and a module which acts as a repository when it's time to find an object by name (a trading ticker in this case, so they're known and formatted, it's not a random string)
I could move the helping member to the module, for example:
member this.LowAllowedPriceDeviation (basePrice: decimal) = this.MinPriceMultiplier * basePrice
could become:
let lowAllowedPriceDeviation basePrice instrument = instrument.MinPriceMultiplier * basePrice
So the object would become simpler and could eventually be turned into a simple storage type without any augmentations.
But I am wondering what are the practical benefits (let's just consider readability, maintainability, etc)?
Also, I don't see how this could be re-structured to not be a class, short of having an 'internal' class in the module and doing all operations through that, but that would just be shifting it.
Your intuition about turning LowAllowedPriceDeviation to a module is correct: it could become a function with the this parameter moved to the end. That is an accepted pattern.
Same goes for all other methods on the Instrument type. And the two private static methods could be come private functions in the module. The exact same approach.
The question "how this could be re-structured to not be a class" confuses me a bit, because this is not actually a class. Instrument is a record, not a class. The fact that you gave it some instance and static methods doesn't make it a class.
And finally (though, technically, this part is opinion-based), regarding "what are the practical benefits" - the answer is "composability". Functions can compose in the way that methods can't.
For example, say you wanted a way to print multiple instruments:
let printAll toString = List.iter (printfn "%s" << toString)
See how it's parametrized with a toString function? That's because I'd like to use it for printing instruments in different ways. For example, I might print their prices:
printAll priceToString (list())
But if PriceToString is a method, I'd have to introduce an anonymous function:
printAll (fun i -> i.PriceToString) (list())
This looks just a little bit more involved than using a function, but in practice it gets very complicated fast. A bigger problem, however, is that this wouldn't even compile because type inference doesn't work on properties (because it can't). In order to get it to compile, you have to add a type annotation, making it even uglier:
printAll (fun (i: Instrument) -> i.PriceToString) (list())
That's just one example of function composability, there are many others. But I'd rather not write a whole blog post on this subject, it's already much longer than I'd like.

MYTYPE of type?

I have the type declaration
type MYVAL = INT of int
and want to perform arithmetic operations on constants and variables of type MYVAL, like
let a : MYVAL = 10
let b : MYVAL = 25
let c = a+b
However, when I run it, it claims that MYVAL does not support the operator +. Isn't MYVAL treated as an integer type? If it is not, what does INT of int do? How would you perform arithmetic operations of variables and constants of type MYVAL?
MYVAL is not treated as an integer type. If that's what you want, you can use a type abbreviation; type MYVAL = int. I'm not sure why you would want to do that, but it's definitely possible.
In your current definition, MYVAL is a single case discriminated union. It wraps a given type, but doesn't inherit any of the underlying type's operators. By the way, the way to construct a an INT is let a = INT 10, not let a : MYINT = 10.
If you want, you can implement your own addition operator, like so
type MYVAL = INT of int with
static member (+) (INT a, INT b) = INT(a+b)
which would allow you to do
let a = INT 10
let b = INT 25
let c = a+b
You would need to do this for any operator you want to use, e.g. (-), (*), etc.
This might all seem a bit confusing, I mean why wouldn't we want the operators to be generated automatically? Well, if you're writing a parser, you might want to be able to read either an int or a string. Such a parser might output a value of a type type MYVAL = INT of int | STRING of string. How would (+) be defined, then? How about (-)?
In the parser example, MYVAL would no longer be a single case discriminated union, as it has multiple cases. A natural question to ask is, why are single case discriminated unions interesting, then? Who would want to use anything like that? Turns out, it's quite neat for subtyping. Say you want to represent a number that's higher than 10. One way to do this is
type HigherThan10 = private Value of int with
static member TryCreate(x: int) =
if x >= 10
then Some(Value(x))
else None
let x = Value(1) // Error
let y = HigherThan10.TryCreate(1) // None
let z = HigherThan10.TryCreate(10) // Some
I know it's not the most interesting example, but it may be used for representing an email adress as a 'subtype' of string. Notice, by the way, how this avoids using exceptions for control flow by returning a HigerThan10 option.
The reason why a simple sum doesn't work was already explained. I'll just show another option: you could define a map2 function for your type:
type MYVAL =
| INT of int
static member map2 f (INT x) (INT y) = INT (f x y)
//This is the correct way to initialize MYVAL, since it is a single-case discriminated union
let a = INT 10
let b = INT 25
//sum
MYVAL.map2 (+) a b //INT 35
//mult
MYVAL.map2 (*) a b //INT 250
//mod
MYVAL.map2 (%) a b //INT 5

What is the difference between float32 vs single and float vs double, if any?

I have always considered the types float32 and single to be interchangeable, in that they are type aliases. The same for float and double. However, they appear to be declared in different assemblies Microsoft.FSharp.Core.Operators vs Microsoft.FSharp.Core.ExtraTopLevelOperators.
Also, the popup description is slightly different, where F# says on float32 and float that it can take a string and use Parse() on it.
However, trying that with single and double succeeds just fine too:
let x = single "12.3"
let y = double "13.4"
Is there any difference I should be aware of? I have always used them interchangeably, never really gave it another thought, until I saw the differences in the popups and in signatures:
// on hovering, or in FSI, this will have the slightly confusing signature:
// val x: a: double -> float
let x (a: double) = float a
All of them are just aliases of the corresponding CLR types as you can see in prim-types-prelude.fs.
type float32 = System.Single
type float = System.Double
type single = System.Single
type double = System.Double
As for the confusing signature consider this:
type typA = A;;
type typB = typA;;
let f (x : typA) = (x : typB)
//val f : x:typA -> typB
Seems like F# prefers to use the aliases at the places you (or some other definition) used them.
Finally the namespaces you are referring to (FSharp.Core.Operators) are referring not to the float type but the float function (float : 'T -> float). See prim-types.fs.

Why does F# constrain my code and remove generics?

type VBO<'T when 'T : (new : unit -> 'T) and 'T : struct and 'T :> ValueType> =
{ Handle : int
target : BufferTarget
size : int
dataSize : int
data : 'T []
pos : int
usage : BufferUsageHint }
type VBO =
static member Create(target, size, pos, usage, (data : Vector3 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
// Type mismatch. Expecting Vector3 but found Vector2
static member Create(target, size, pos, usage, (data : Vector2 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
// This construct causes code to be less generic than indicated by the type annotations.
// The type variable 'T has been constrained to be type 'Vector3'.
static member CreateImpl(target, size, pos, usage, dataSize, (data : 'T [])) =
let h = GL.GenBuffer()
{ Handle = h
target = target
size = size
dataSize = dataSize
data = data
pos = pos
usage = usage }
F# tries to constrain my code but I want it to be generic. I don't really care about what type the data is I just need it pass in the correct dataSize.
What have I done wrong?
The F# compiler seems to specialise generic types if they are used at a particular instantiation in the same block of code. Try splitting up your declaration like this:
type VBO<'T when 'T : (new : unit -> 'T) and 'T : struct and 'T :> ValueType> =
{ Handle : int
target : BufferTarget
size : int
dataSize : int
data : 'T []
pos : int
usage : BufferUsageHint }
type VBO =
static member CreateImpl(target, size, pos, usage, dataSize, (data : 'T [])) =
let h = GL.GenBuffer()
{ Handle = h
target = target
size = size
dataSize = dataSize
data = data
pos = pos
usage = usage }
type VBO with
static member Create(target, size, pos, usage, (data : Vector3 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
static member Create(target, size, pos, usage, (data : Vector2 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
F#'s type inference works top-to-bottom and left-to-right, which occasionally leads to interesting corner cases. In particular, reordering method definitions within a type can affect the inferred types. As Ganesh has pointed out, one solution to your problem is to break your type up into disjoint chunks, but this isn't actually necessary - just putting the generic method first should be sufficient (and note that you can also drop the type annotation on data, if desired).
You can see very similar behavior with simple let rec bindings. Consider:
let rec f() = h 5
and g() = h "test"
and h x = x
When attempting to infer the type of f, the compiler notes that h must take an int as input, which makes g's definition invalid. Reordering the definitions to put h first is the easiest solution (then the compiler can infer a generic type for h before looking at the bodies of f and g, so everything goes through fine).
Alternatively, you can explicitly make h generic and add type annotations to help the compiler out:
let rec f() = h 5
and g() = h "test"
and h<'t> (x:'t) :'t = x
This can be inconvenient since it can require a significant annotation overhead, but there are circumstances where simply reordering definitions is insufficient to allow the compiler to infer the correct types, so it may sometimes be necessary.
I think it is better to look at the code to understand what went wrong.
So here is a simpler example that shows the same problem.
type test =
static member test (data:int) = test.actual(data)
static member test (data:float) =test.actual(data)
static member actual (data:'t) = ()
the problem is that for static member functions, all the types need to be known - or you need a generic type.
I think the simplest solution is to change your code to look like
let actual (data:'t) = ()
type test =
static member test (data:int) = actual(data)
static member test (data:float) =actual(data)
Here, the compiler has much more freedom to alter the let binding to make it generic.

Converting from m/s to km/h using F# Units of Measure

I'm in the process of learning F# - and is currently looking into Units of Measure. I have a simple calculation returning meters per second, and I want to introduce a function converting it to kilometres per hour.
My code looks like this:
[<Measure>] type kg
[<Measure>] type s
[<Measure>] type m
[<Measure>] type km
[<Measure>] type h
let msToKmph(speed : float<m/s>) =
(float speed) * 3.6<km/h>
let gravityOnEarth = 9.81<m/s^2>
let heightOfJump = 3.5<m>
let speedOfImpact = sqrt (2.0 * gravityOnEarth * heightOfJump)
let speedOfImpactKmh = msToKmph(speedOfImpact)
This works - I get 8.28673639 m/s and 29.832251 km/h. What I am unsure of is if this is the best way to express the relationship between different units. Can this be done more elegantly?
For instance, the line doing (float speed) to remove the unit information from the speed parameter, to make the msToKmph return km/h. If I did not remove unit information before doing the calculation, the returned unit would be: km m/(h s)
First, your msToKmph is totally incorrect. Although it returns a correct return value, what it is actually doing, is it just drops the original <m/s> value by converting to a plain, measureless float and then multiplies the measureless value to a 3.6<km/h>.
To better express the relations between UoM's, consider this:
let kmToM = 1000.0<m/km> // relation between kilometers and meters
let hrToSec = 3600.0<s/h> // relation between seconds and hours
let msToKmph(speed : float<m/s>) =
speed / kmToM * hrToSec
Note, all "magic numbers" are encapsulated within UoM converters, hence your formulas remain clean, e.g. they simply operate values and constants, but the UoM are calculated by the compiler.
Update: The philosophy of UoM conversion is that the conversion formulas should be something that has physical sense. The rule of thumb is whether your conversion value presents in reference books. In plain English, 3.6<km/h> from above is useless, but 1000.0<m/km> just says, "there is 1000 m in 1 km", which makes sense.
You can even improve hrToSec like this:
let hrToSec2 = 60.0<s/minute> * 60.0<minute/h>
This will make every value a well-known value found in reference books.
You're right that removing unit information is a bad thing. You should create a few constants with appropriate units for conversion.
let mPerKm = 1000.0<m/km>
let secondPerHour = 3600.0<s/h>
// val msToKmph : float<m/s> -> float<km/h>
let msToKmph(speed : float<m/s>) =
speed / mPerKm * secondPerHour
For km and m, a generic solution is to define a unit prefix k so it works for many UoMs which have kilo as a metric:
[<Measure>] type k
let kilo = 1000.0<1/k>
let secondPerHour = 3600.0<s/h>
// val msToKmph : float<m/s> -> float<k m/h>
let msToKmph(speed : float<m/s>) =
speed / kilo * secondPerHour

Resources