2011-01-27 12 views
5

Estoy convirtiendo algunos códigos Java en Scala, intentando hacer el código lo más idiomático posible.Manera idiomática de usar Options en Scala

Entonces, ahora tengo un código que usa Opciones en lugar de valores que aceptan valores de null, y me pregunto si las cosas son scala'ish, o si estoy equivocado. Entonces, ¿podrían criticar el siguiente fragmento de código?

Las áreas en las que estoy buscando específicamente para la retroalimentación son:

  • El uso de un objeto acompañante como una fábrica, dando 2 opciones dependiendo de si queremos pasar Opciones o Cuerdas: es el constructor de String bien, o deberíamos siempre exponer el hecho de que es una opción?
  • El uso de condiciones previas: ¿hay formas mejores de afirmar el hecho de que alpha3Code y name son obligatorios, y se debe pasar una opción no nula para alpha2Code? (Estoy recurriendo a Guava para el string utils, ya que no he encontrado nada en la API de Scala)
  • La implementación de hashCode, equals y toString. equals y toString delegan a Guava nuevamente, mientras que equals usa la coincidencia de patrones. ¿Hay una forma más escalada?
  • Sé que podría haber usado clases Case, lo que habría creado implementaciones predeterminadas, pero estoy interesado principalmente en aprender cómo debo implementarlas para los casos donde las clases de casos no se pueden usar.

¡Muchas gracias!

package com.sirika.openplacesearch.api.language 

import com.google.common.base.Objects 
import com.google.common.base.Strings 

object Language { 
    def apply(name : String, alpha3Code : String, alpha2Code : Option[String]) = new Language(name, alpha3Code, alpha2Code) 
    def apply(name : String, alpha3Code : String, alpha2Code : String = null) = new Language(name, alpha3Code, Option(alpha2Code)) 
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code) 
} 


class Language(val name : String, val alpha3Code : String, val alpha2Code : Option[String]) { 
    require(!Strings.isNullOrEmpty(alpha3Code)) 
    require(!Strings.isNullOrEmpty(name)) 
    require(alpha2Code != null) 

    override def hashCode(): Int = Objects.hashCode(alpha3Code) 

      override def equals(other: Any): Boolean = other match { 
     case that: Language => this.alpha3Code == that.alpha3Code 
     case _ => false 
    } 

    override def toString() : String = Objects.toStringHelper(this) 
     .add("name", name)  
     .add("alpha3", alpha3Code) 
     .add("alpha2", alpha2Code) 
     .toString() 
} 
+1

El "truco" para el uso de las opciones es que sólo las opciones de uso y la fuerza de los consumidores a hacer lo mismo ;-) Por supuesto, esto no es siempre práctico cuando se trata de Java (ick!). Bienvenido a SO. –

+0

No creo que require (alpha2Code! = Null) pueda fallar alguna vez ya que alpha2Code es una Opción – Azzie

Respuesta

4

Creo que debe exponer solo Option[String] en el método de fábrica. Por ejemplo, yo, como usuario de su biblioteca, también me pregunto qué método de fábrica debería usar. Y muy probablemente usaré Option.

Scala nos brinda suficientes herramientas para facilitar nuestras vidas. Por ejemplo, puede usar por defecto para la opción de la siguiente manera:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
new Language(name, alpha3Code, alpha2Code) 

Si, de nuevo como usuario de la biblioteca, quiero pasar secuencia apenas sin envolverlo en Some cada vez, puedo escribir mi propia conversión implícita como esto :

implicit def anyToOption[T](t: T): Option[T] = Some(t) 

o incluso (si yo personalmente uso nulos):

implicit def anyToOption[T](t: T): Option[T] = 
if (t == null) None else Some(t) 

Pero creo que, si la opción de hacer cumplir, que hará que su API más sólida y clara.

+3

En realidad, hay una manera más fácil de ajustar un objeto en 'Option' de modo que un valor' null' se convertirá en 'None': 'Option (t)' (en lugar de 'Some (t)') – Madoc

4

Debe evitar null a menos que haya una muy buena razón para no hacerlo. Tal como está, usted podría haber escrito esto:

def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, Option(alpha2Code)) 
def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code, None) 

Las condiciones previas están bien. Podría escribirlo así:

require(Option(alpha3Code) exists (_.nonEmpty)) 
require(Option(name) exists (_.nonEmpty)) 

Sin embargo, no necesariamente es una mejora.

A String tiene , así que no entiendo por qué está llamando a otro método para generar un código hash en lugar de simplemente llamar al alpha3Code.hashCode. Sin embargo, creo que hay algo en la API de Scala. No es seguro.

El código equals debe tener un método canEqual, a menos que haga su clase sealed o final. el ajuste de patrones es más o menos la forma de hacerlo, a pesar de que podría haber escrito como esto dada la presencia de un extractor:

case Language(_, `alpha3Code`, _) => true 

Pero la forma en que lo escribió es más o menos la forma en que normalmente se escribe.

+0

Las otras respuestas parecen enfatizar el hecho de que realmente debería forzar al consumidor a usar el constructor de Option. ¿Estás de acuerdo con esa afirmación? –

+0

En cuanto a hashCode, es cierto que en este caso, podría usar alpha3Code.hashCode directamente, ni siquiera lo pensé, ya que tengo el hábito de usar siempre la sintaxis de guayaba, lo que permite agregar varias claves: Objects.hashCode (alpha3Code, nombre) –

+0

Y buen punto sobre el método canEqual. Yo no era consciente de ello. Para otras personas que no conocen el canEqual, puede consultar http://books.google.com/books?id=MFjNhTjeQKkC&pg=PA555&lpg=PA555&dq=scala+canEqual+method&source=bl&ots=FKukUGGNvo&sig=0rF3cRgwHVGZ22ggxX5j23nM32w&hl=en&ei=As1CTaWhMoPGlQfknZ35Dw&sa= X & oi = book_result & ct = result & resnum = 3 & ved = 0CCcQ6AEwAg # v = onepage & q = scala% 20canEqual% 20method & f = false –

-1

No me gustan las opciones: agregan un nivel de direccionamiento indirecto que es innecesario y confuso en muchos casos. No me gustan mucho más los nulos, así que entiendo que a menudo el uso de Opciones está justificado. Sin embargo, siempre debe ver si hay una forma más natural de eliminar el uso de Option en una interfaz.

Los parámetros predeterminados o las sobrecargas separadas a menudo son una mejor opción. Así que me gustaría volver a escribir el código de la siguiente manera:

package com.sirika.openplacesearch.api.language 

import com.google.common.base.Strings 
import com.google.common.base.Objects 

object Language { 
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code) 
    def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code) 
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code) 
} 


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) { 
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code)) 
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None) 

    require(!Strings.isNullOrEmpty(alpha3Code)) 
    require(!Strings.isNullOrEmpty(name)) 

    override def hashCode = alpha3Code.hashCode 

    override def equals(other: Any) = other match { 
     case that: Language => this.alpha3Code == that.alpha3Code 
     case _ => false 
    } 

    override def toString = MoreObjects.toStringHelper(this) 
     .add("name", name)  
     .add("alpha3", alpha3Code) 
     .add("alpha2", alpha2Code) 
     .toString() 
} 

Guava docs

Cuestiones relacionadas