f# - ¿Es posible hacer cumplir que un Registro respeta a algunos invariantes?
record refinement-type (3)
Aquí hay otra solución basada en niveles de protección:
module MyModule =
type Bounds = private { _min: float; _max: float } with
// define accessors, a bit overhead
member public this.Min = this._min
member public this.Max = this._max
static member public Make(min, max) =
if min > max then raise (ArgumentException("bad values"))
{_min=min; _max=max}
// The following line compiles fine,
// e.g. within your module you can do "unsafe" initialization
let myBadBounds = {_min=10.0; _max=5.0}
open MyModule
let b1 = Bounds.Make(10.0, 20.0) // compiles fine
let b1Min = b1.Min
let b2 = Bounds.Make(10.0, 5.0) // throws an exception
// The following line does not compile: the union cases of the type ''Bounds''
// are not accessible from this code location
let b3 = {_min=10.0; _max=20.0}
// The following line takes the "bad" value from the module
let b4 = MyModule.myBadBounds
Supongamos que quisiera crear un tipo de registro que represente los límites mínimo / máximo aceptables:
type Bounds = { Min: float; Max: float }
¿Hay una manera de hacer cumplir ese Min <Max? Es fácil escribir una función validateBounds, me preguntaba si había una mejor manera de hacerlo.
Edit: Me di cuenta de que para este ejemplo específico probablemente podría salirme con la idea de exponer dos propiedades y reordenar los argumentos, así que digamos que tratamos de hacerlo
type Person = { Name: string }
y el nombre debe tener al menos un carácter.
Creo que tu mejor apuesta es un miembro estático:
type Bounds = { Min: float; Max: float }
with
static member Create(min: float, max:float) =
if min >= max then
invalidArg "min" "min must be less than max"
{Min=min; Max=max}
y usalo como
> Bounds.Create(3.1, 2.1);;
System.ArgumentException: min must be less than max
Parameter name: min
at FSI_0003.Bounds.Create(Double min, Double max) in C:/Users/Stephen/Documents/Visual Studio 2010/Projects/FsOverflow/FsOverflow/Script2.fsx:line 5
at <StartupCode$FSI_0005>.$FSI_0005.main@()
Stopped due to error
> Bounds.Create(1.1, 2.1);;
val it : Bounds = {Min = 1.1;
Max = 2.1;}
Sin embargo, como señala, el gran inconveniente de este enfoque es que no hay nada que impida la construcción de un registro "no válido" directamente. Si esta es una preocupación importante, considere usar un tipo de clase para garantizar sus invariantes:
type Bounds(min:float, max:float) =
do
if min >= max then
invalidArg "min" "min must be less than max"
with
member __.Min = min
member __.Max = max
junto con un patrón activo por conveniencia similar a lo que se obtiene con los registros (específicamente con respecto a la coincidencia de patrones):
let (|Bounds|) (x:Bounds) =
(x.Min, x.Max)
todos juntos:
> let bounds = Bounds(2.3, 1.3);;
System.ArgumentException: min must be less than max
Parameter name: min
at FSI_0002.Bounds..ctor(Double min, Double max) in C:/Users/Stephen/Documents/Visual Studio 2010/Projects/FsOverflow/FsOverflow/Script2.fsx:line 4
at <StartupCode$FSI_0003>.$FSI_0003.main@()
Stopped due to error
> let bounds = Bounds(1.3, 2.3);;
val bounds : Bounds
> let isMatch = match bounds with Bounds(1.3, 2.3) -> "yes!" | _ -> "no";;
val isMatch : string = "yes!"
> let isMatch = match bounds with Bounds(0.3, 2.3) -> "yes!" | _ -> "no";;
val isMatch : string = "no"
Una solución poco fiable para el ejemplo de cadena: use un DU
type cleverstring = |S of char * string
Esto forzará a la cadena a tener al menos un personaje. Luego, solo puede usar cleverstring
lugar de una string
en su registro, aunque probablemente desee escribir algunas funciones de envoltura para que se vea como una cadena.