f# record refinement-type

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.