types - precio - F#: Mejores prácticas para organizar tipos y módulos
memoria ram ddr4 8gb 2400mhz (1)
Si tomo el enfoque funcional de definir un tipo, y luego describir las funciones (en oposición a los métodos de instancia) que operan en ese tipo, ¿cómo debería organizar mi código?
En general, voy de tres maneras:
(1):
module MyType =
type MyType (x : int) =
member val X = x with get
let myFunction myType y = myType.X + y
(2):
type MyType (x : int) =
member val X = x with get
[<RequireQualifiedAccess>]
module MyTypeOps =
let myFunction myType y = myType.X + y
(3):
type MyType =
{
x : int
}
static member MyFunction myType y = myType.x + y
Pro de (1) es que las funciones se definen en un módulo. Contras de (1) son que el tipo también está definido en el módulo, lo que resulta en redundancia fea de MyType.MyType
en la MyType.MyType
de instancias y en la incapacidad de [<RequireQualifiedAccess>]
si se quiere permitir open MyType
como una open MyType
para esa redundancia .
Las ventajas de (2) son funciones definidas en un módulo y ninguna redundancia de tipo módulo. Con es que el módulo no puede ser del mismo nombre que el tipo.
Pro de (3) es que los métodos son estáticos y no hay redundancia de módulo. Las desventajas son que no puede definir algunos tipos (por ejemplo, valores sin método y patrones activos) bajo este método.
Normalmente, prefiero (2), aunque odio tener que nombrar el módulo algo menos descriptivo e intuitivo de lo que debería ser. (2) también me permite crear tipos mutuamente dependientes usando el type ... and ...
en el raro caso de que necesite hacer eso, lo cual es obviamente imposible para los tipos definidos en módulos separados como el enfoque (1).
¿Qué enfoque toman mis compañeros F # programadores? Me pregunto si estoy pasando por alto algo obvio (o no tan obvio), o, si no lo estoy, si hay una convención que aborde la incapacidad de nombrar un módulo con el mismo nombre que el tipo correspondiente en el interior el mismo espacio de nombres
Hay una cuarta manera que no ha enumerado, que es tener el tipo y el módulo con el mismo nombre. Piensas que no se puede hacer, pero en realidad es una práctica muy común; simplemente tiene que (en versiones F # anteriores a la 4.1) usar el atributo [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
en el módulo. Es un poco feo escribir eso en todas partes, y es por eso que F # 4.1 lo convirtió en el comportamiento predeterminado si defines un tipo con el mismo nombre que un módulo. Para ver cómo se hace, mire el código FSharpx.Collections (entre muchos otros proyectos). Aquí hay un archivo que no es terriblemente grande, eso lo convierte en un buen ejemplo:
https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs
namespace FSharpx.Collections
type Queue<''T> (front : list<''T>, rBack : list<''T>) =
// ...
member this.Conj x =
match front, x::rBack with
| [], r -> Queue((List.rev r), [])
| f, r -> Queue(f, r)
// ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Queue =
// ...
let inline conj (x : ''T) (q : Queue<''T>) = (q.Conj x)
// ...
Cuando organizas tu código de esta manera, con un tipo y módulo del mismo nombre, el compilador F # es perfectamente capaz de mantener las cosas en orden, especialmente si sigues las convenciones de nomenclatura estándar, teniendo métodos de miembros nombrados en estilo PascalCase pero teniendo funciones en el módulo nombrado en estilo camelCase. El compilador de C # se confundiría, pero el atributo CompilationRepresentation
se encarga de eso, asegurando que otros lenguajes .Net vean el módulo con el nombre QueueModule
. Entonces desde F #:
let q = new Queue([1;2;3], [])
let moreItems = q |> Queue.conj 4
let stillMoreItems = moreItems.Conj 5
Desde C#:
Queue<int> q = new Queue<int>({1,2,3}, {}); // Not valid list syntax, but whatever
Queue<int> moreItems = QueueModule.Conj(4, q);
Queue<int> stillMoreItems = moreItems.Conj(5);
La función Queue.conj
parece más natural de usar desde F #, y el método member moreItems.Conj
parece más natural de usar desde C #, pero ambos están disponibles en ambos idiomas.