2011-12-21 10 views
13

Los elementos en las tuplas no tienen nombres, lo que significa que a menudo no tienes una forma clara de documentar los significados de cada elemento.¿Es una práctica estándar usar alias de tipo para indicar la semántica de los parámetros?

Por ejemplo, en esta unión discriminados:

type NetworkEvent = 
| Message of string * string * string 
| ... 

Quiero dejar claro que el primer y segundo elementos son el emisor y nombres de los destinatarios, respectivamente. Es una buena práctica para hacer algo como esto:

type SenderName = string 
type RecipientName = string 

type NetworkEvent = 
| Message of SenderName * RecipientName * string 
| ... 

Mucha/C bibliotecas de C++ tienen una proliferación de tipos (por ejemplo win32.h), pero en esos idiomas, a pesar de que los nombres de parámetros son opcionales en muchos casos , todavía se puede hacer. Ese no es el caso con F #.

+3

En F # 3.1, estos pueden tener nombres. –

+0

@JamesMoore Sí, eso me hace feliz: D –

Respuesta

10

Creo que el uso de alias de tipo para fines de documentación es una manera buena y sencilla para documentar sus uniones discriminadas.Utilizo el mismo enfoque en muchas de mis demos (vea for example this one) y sé que algunas personas también lo usan en aplicaciones de producción. Creo que hay dos maneras de hacer que la definición más fáciles de entender:

Use alias de tipo: De esta manera, se agrega algún tipo de documentación que es visible en el IntelliSense, pero no se propaga a través del sistema de tipos - cuando usó un valor del tipo con alias, el compilador lo tratará como string, por lo que no verá la documentación adicional en todas partes.

Utilice uniones de caja única Este es un patrón que se ha utilizado en algunos lugares del compilador de F #. Esto hace que la información sea más visible que el uso de Tipo-alias, debido a un tipo SenderName es en realidad un tipo diferente de string (por el contrario, esto puede tener alguna pequeña penalización de rendimiento):

type SenderName = SenderName of string 
type RecipientName = RecipientName of string 
type NetworkElement = 
    | Message of SenderName * RecipietName * string 

match netelem with 
| Message(SenderName sender, RecipientName recipiet, msg) -> ... 

Use registros: De esta manera, define explícitamente un registro para llevar la información de un caso de unión. Esto es más sintácticamente prolijo, pero probablemente agrega la información adicional de la manera más accesible. Aún puede usar la coincidencia de patrones en los registros, o puede usar la notación de puntos para acceder a los elementos. También es más fácil agregar nuevos campos durante el desarrollo:

type MessageData = 
    { SenderName : string; RecipientName : string; Message : string } 
type NetworkEvent = 
    | Message of MessageData 

match netelem with 
| Message{ SenderName = sender; RecipientName = recipiet; Message = msg} -> ... 
+3

Basado en los votos positivos, esta es probablemente una vista minoritaria, pero la verbosidad de los registros o uniones de casos únicos es un factor decisivo en mi mente. Niega por completo la concisión/conveniencia sintáctica de los sindicatos. Este parece ser un problema que Intellisense mejor solucionó, siempre que haya una forma de anotar los campos, sin cambiar los tipos y el estilo de codificación. – Daniel

+1

Creo que esta (por lo demás excelente) respuesta puede necesitar una actualización. Ahora puede hacer 'type X = Msg of Sender: string * Recip: string'. Y también puede usar estos campos: 'let x = X (Sender =" Foo ", Recip =" Bar ")'. Al igual que los registros, pero con una sintaxis diferente. – Abel

1

Creo que en este caso sería mejor usar un tipo de registro para los primeros dos elementos de la tupla para reforzar el hecho de que el orden importa.

Para los números, se puede hacer algo un poco más elegante usando unidades de medida, pero esto no funciona para las cadenas

+0

¿No sería una proliferación de registros igual de mala, si no peor, si hay una gran cantidad de 'NetworkEvents'? La inferencia de tipo se confunde mucho cuando hay toneladas de registros con nombres de parámetros similares, y terminas teniendo que calificar completamente cada campo. –

+0

@ReiMiyasaka - en realidad la situación no es tan mala. Si califica completamente el primer campo que menciona al crear la instancia del registro, los demás se resolverán correctamente. – Kit

1

no veo nada malo en esto, pero podría ser molesto que tienen 10 alias para string, por ejemplo. Si eso no te molesta, digo, adelante. Personalmente, me gustaría que se admitiera algo como lo siguiente:

type NetworkEvent = 
| Message of SenderName:string * RecipientName:string * string 
| ... 

Entonces Intellisense podría ofrecer alguna ayuda. (EDITAR: Votar esta sugerencia here.)

Si sus casos tienen más de unos pocos campos, o varios del mismo tipo, puede considerar el uso de una jerarquía de clases en su lugar.

+0

Sí, hay muchos aspectos de la sintaxis F # que hacen que sea imposible tener un IntelliSense tan rico como C#. –

+0

Hay una mejora reciente que permite esto: "A partir de F # 3.1, puede asignar un nombre a un campo individual, pero el nombre es opcional, incluso si se nombran otros campos en el mismo caso". http://msdn.microsoft.com/en-us/library/dd233226.aspx –

6

He leído mi tarifa compartida de F #, tanto en internet como en libros, pero nunca he visto a nadie usar alias como una forma de documentación. Así que voy a decir que no es una práctica estándar. También podría verse como una forma de duplicación de código.

En general, una representación de tupla específica solo se debe utilizar como una estructura de datos temporal dentro de una función. Si está almacenando una tupla durante un tiempo prolongado o pasándola entre diferentes clases, entonces es hora de hacer un registro.

Si va a utilizar una unión discriminada en varias clases, utilice registros como sugirió o mantenga todos los métodos con el alcance de la unión discriminada como se muestra a continuación.

type NetworkEvent = 
    | Message of string * string * string 

    static member Create(sender, recipient, message) = 
     Message(sender, recipient, message) 

    member this.Send() = 
     math this with 
     | Message(sender, recipient, message) -> 
      printf "Sent: %A" message 

let message = NetworkEvent.Create("me", "you", "hi") 

Puede utilizar records in pattern matching, por lo que las tuplas son realmente una cuestión de conveniencia y deben ser sustituidos por los registros como el código crece.

Si una unión discriminada tiene un montón de tuplas con la misma firma, entonces es hora de dividirla en dos uniones discriminadas. Esto también le impedirá tener múltiples registros con la misma firma.

type NetworkEvent2 = 
    | UDPMessage of string * string * string 
    | Broadcast of string * string * string 
    | Loopback of string * string * string 
    | ConnectionRequest of string 
    | FlushEventQueue 

en

type MessageType = 
    | UDPMessage 
    | Broadcast 
    | Loopback 

type NetworkEvent = 
    | Message of MessageType * string * string * string 
    | ConnectionRequest of string 
    | FlushEventQueue 
Cuestiones relacionadas