2011-07-01 9 views
8

Esto es más una cuestión de diseño que cualquier otra cosa ...Uso de clases de casos de Scala como mapas de facto

Me gustan mucho las clases de casos de Scala y las uso a menudo. Sin embargo, me parece que a menudo estoy envolviendo mis parámetros en Options (o mejor dicho, Lift's Boxes) y estableciendo valores predeterminados para permitir flexibilidad y dar cuenta de que un usuario no siempre puede especificar todos los parámetros. Creo que adopté esta práctica de.

Mi pregunta es, ¿es este un enfoque razonable? Dado que todo puede ser opcional, puede haber un montón de repetición y verificación, hasta el punto de que me pregunto si no estoy usando mis clases de casos como Map[String, Any] y me pregunto si no sería mejor usar un Map.

Déjeme darle un verdadero ejemplo. Aquí estoy modelando una transferencia de dinero:

case class Amount(amount: Double, currency: Box[Currency] = Empty) 
trait TransactionSide 
case class From(amount: Box[Amount] = Empty, currency: Box[Currency] = Empty, country: Box[Country] = Empty) extends TransactionSide 
case class To(amount: Box[Amount] = Empty, currency: Box[Currency] = Empty, country: Box[Country] = Empty) extends TransactionSide 
case class Transaction(from: From, to: To) 

Relativamente simple de entender, creo. En este simple podríamos declarar una Transaction así:

val t = Transaction(From(amount=Full(Amount(100.0)), To(country=Full(US))) 

ya que puede imaginar usted piensa que es prolijo. Y si especificamos todo:

val t2 = Transaction(From(Full(Amount(100.0, Full(EUR))), Full(EUR), Full(Netherlands)), To(Full(Amount(150.0, Full(USD))), Full(USD), Full(US))) 

Por otro lado, a pesar de tener que tirar Full por todas partes, todavía se puede hacer algo agradable coincidencia de patrones:

t2 match { 
    case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(country_from)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(country_to))) if country_from == country_to => Failure("You're trying to transfer to the same country!") 
    case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(US)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(North_Korea))) => Failure("Transfers from the US to North Korea are not allowed!") 
    case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(country_from)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(country_to))) => Full([something]) 
    case _ => Empty 
} 

Es este un enfoque razonable? ¿Me serviría mejor usando un Map? ¿O debería usar clases de casos pero de una manera diferente? ¿Tal vez usando una jerarquía completa de clases de casos para representar transacciones con diferentes cantidades de información especificadas?

Respuesta

4

El uso de una clase de caso es menos flexible que un mapa, ya que solo puede asignar/acceder a campos predefinidos. Necesitará construir una jerarquía de clases de casos completos de antemano.

Por otro lado, la clase de caso ofrece tipo de "validaciones en tiempo de compilación", porque todos los tipos están explícitamente definidos (en contraste con Map[String,Any]), y no puede asignar/acceder a un campo no especificado por error. Las clases de casos también deberían ser más rápidas, ya que no es necesario atravesar la tabla hash del mapa para encontrar lo que está buscando.

El problema de "verbosidad" proviene del aspecto inmutable de las clases de casos, pero tendrá exactamente el mismo problema con los mapas inmutables. La solución parece ser Lentes. Hay una muy buena charla aquí:

http://www.youtube.com/watch?v=efv0SQNde5Q&list=PLEDE5BE0C69AF6CCE

+0

Gracias por el enlace a la charla de Lentes, lo estoy escuchando ahora. ¿Pero hay páginas web con un tutorial? Me gustaría aprender más! – pr1001

+0

Nunca los encontré para scala, pero debería existir para haskell. – paradigmatic

5

Si algo es realmente opcional, entonces realmente no tienen otra opción. null es no una opción (sin juego de palabras).

Deseo fuertemente desaconsejamos el uso del tipo de caja de ascensor, a menos que lo necesite para tratar específicamente con Lift APIs. Solo estás presentando una dependencia innecesaria.

También me gustaría pensar seriamente si realmente tiene sentido tener un Amount sin una moneda específica. Si es válido, entonces la creación de un "objeto nulo" dedicada a representar una moneda no especificada le daría una API más limpio:

class LocalCurrency extends Currency 

alternativa:

sealed trait Amount 
case class LocalisedAmount(value: Double, currency: Currency) extends Amount 
case class RawAmount(value: Double) extends Amount 

Para los TransactionSide subclases, encuentro Es extraño que pueda especificar Currency por separado de Amount (que ya incorpora la noción de moneda). Me favor:

case class TxEnd(
    amount: Option[Amount] = None, 
    country: Option[Country] = None) 
case class Transaction(from: TxEnd, to: TxEnd) 

Finalmente ...

Sí, mapas de uso si encajan bien con su dominio, que va a hacer para el código mucho más limpio.

+0

Gracias, sus sugerencias suenan muy razonables. La razón por la que tengo una segunda moneda es porque puedo analizar cadenas como '$ 100 a DE' o' 100 USD a DE'. 'LocalizedAmount' y' RawAmount' podrían ayudar, pero no estoy seguro. En este momento estoy haciendo coincidir las cadenas con los extractores: 'msg.split (" ") .toList coincidencia {case Amount (amount_in) :: Currency (currency_in) ::" to ":: Country (country_out) :: Nil => ...} '. Me gusta mucho y no sé si puedo evitar la segunda 'Moneda'. – pr1001

+0

En cuanto a Lift's Box, me gusta 'Failure' mucho y este código está en el contexto de una aplicación Lift de todos modos ... – pr1001

+0

Como alternativa a' Box', considere 'Oither [Exception, Option [T]]'. la ventaja es que puede mapear la proyección izquierda (por ejemplo 'myEither.left map {_.getMessage}') para producir un 'Oither [String, Option [T]]' - adecuado para presentar como un mensaje de error a un usuario. –

Cuestiones relacionadas