son significado que punteros puntero operadores operador los lenguaje flecha estructura direccion desreferenciar declarar datos como scala groovy

significado - ¿Mejor imitación de Scala del operador de desreferencia segura de Groovy(?).



que es un puntero en c (8)

Me gustaría saber cuál es la mejor imitación de Scala del operador de desreferencia segura de Groovy (?.) , O al menos algunas alternativas cercanas.

Lo he discutido brevemente en el blog de Daniel Spiewak , pero me gustaría abrirlo a StackOverFlow ...

Por el bien de todos, aquí está la respuesta inicial de Daniel, mi contador y su segunda respuesta:

@Antony

En realidad, miré hacer eso primero. O más bien, estaba tratando de replicar el "operador" de Ragenwald desde la tierra de Ruby. El problema es que esto es un poco difícil de hacer sin proxies. Considere la siguiente expresión (usando el andand de Ruby, pero es lo mismo con el operador de Groovy):

test.andand (). doSomething ()

Podría crear una conversión implícita de Any => algún tipo que implemente el método andand (), pero ahí es donde se detiene la magia. Independientemente de si el valor es nulo o no, el método doSomething () se ejecutará. Ya que tiene que ejecutarse en algún objetivo de una manera segura, eso requeriría la implementación de un proxy de bytecode, que sería escamoso y extraño (problemas con anotaciones, métodos finales, constructores, etc.).

Una mejor alternativa es regresar a la fuente de inspiración tanto para el operador de desreferencia seguro de Groovy como para el operador de desreferencia segura de Groovy: la operación del mapa monádico. La siguiente es alguna sintaxis de Scala que utiliza la opción para implementar el patrón:

val something: Option [String] = ... // presumiblemente podría ser Some (...) or None

val length = something.map (_. length)

Después de esto, la length será Some (str.length) (donde str es el objeto String incluido dentro de la opción) o None. Así es exactamente como funciona el operador de eliminación de referencias seguras, excepto que utiliza una mónada nula en lugar de segura.

Como se señaló anteriormente, podríamos definir una conversión implícita de algún tipo T => Opción [T] y luego mapear de esa manera, pero algunos tipos ya tienen un mapa definido, por lo que no sería muy útil. Alternativamente, podría implementar algo similar al mapa pero con un nombre diferente, pero de cualquier forma que se implemente, dependerá de una función de orden superior en lugar de una simple llamada encadenada. Parece ser solo la naturaleza de los lenguajes estáticos (si alguien tiene una forma de evitar esto, no dude en corregirme).

Daniel Spiewak Lunes, 7 de julio de 2008 a las 1:42 p.m.

Mi segunda pregunta:

Gracias por la respuesta de Daniel con respecto? ¡Creo que lo extrañé! Creo que entiendo lo que propones, pero ¿qué tal algo así, suponiendo que no tienes control sobre las fuentes?

company?.getContactPerson?.getContactDetails?.getAddress?.getCity

Digamos que es un frijol java y no puedes entrar y cambiar los valores de retorno a algo [T]. ¿Qué podemos hacer allí?

Antony Stubbs Martes, 21 de julio de 2009 a las 8:07 p.m. oh Dios mío - está bien en volver a leer que es donde está proponiendo la conversión implícita de T a la opción [T] ¿verdad? ¿Pero aún así podrías encadenarlo así? Todavía necesitarías el mapa ¿verdad? hmm ...

var city = company.map(_.getContactPerson.map(_.getContactDetails.map(_.getAddress.map(_.getCity))))

?

Antony Stubbs Martes, 21 de julio de 2009 a las 8:10 p. M.

Su segunda respuesta:

@Antony

Realmente no podemos hacer mucho de nada en el caso de la compañía? .GetContactPerson, etc ... Incluso suponiendo que esto fuera una sintaxis de Scala válida, todavía necesitaríamos alguna forma de evitar las llamadas posteriores en la cadena. Esto no es posible si no estamos usando valores de función. Por lo tanto, algo así como el mapa es realmente la única opción.

Una conversión implícita a Option no sería mala, pero al hacer las cosas implícitas, eludimos parte de la protección del sistema de tipos. La mejor manera de hacer este tipo de cosas es utilizar para-comprensiones en concierto con Option. Podemos hacer map y flatMap, pero es mucho más agradable con sintaxis mágica:

for { c < - company person <- c.getContactPerson details <- person.getContactDetails address <- details.getAddress } yield address.getCity

Daniel Spiewak Martes, 21 de julio de 2009 a las 9:28 p.m.

PD: si Daniel publica sus respuestas originales en su blog como respuestas, editaré la pregunta para eliminarlas por el bien del Sistema.


¿Qué tal esto?

def ?[A](block: => A) = try { block } catch { case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null case e => throw e }

Al usar este pequeño fragmento, puede desreferenciar de manera segura y el código en sí es bastante breve:

val a = ?(b.c.d.e)

a == nulo si b o bc o bcd o bcde es nulo, de lo contrario, a == bcde

Creo que el valor de un operador de eliminación de referencias está disminuido cuando está usando un lenguaje como Scala que tiene funciones como llamadas por nombre e implícitos.

ps: modifico un poco el código anterior a la luz de uno de los comentarios a continuación para manejar el caso cuando NullPointerException realmente se arroja dentro de la función llamada.

Por cierto, creo que usar la función a continuación es una forma más idiomática de escribir Scala:

def ??[A](block: => A): Option[A] = ?(block) match { case a: A => Some(a) case _ => None }

al igual que:

??(a.b.c.d) match { case Some(result) => // do more things with result case None => // handle "null" case }


Crea esta conversión implícita

class SafeDereference[A](obj: A) { def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj) } implicit def safeDereference[A](obj: A) = new SafeDereference(obj)

El uso no es tan bonito como Groovy, pero no es horrible.

case class Address(state: String) case class Person(first: String, last: String, address: Address) val me = Person("Craig", "Motlin", null) scala> me ? (_.first) res1: String = Craig scala> me ? (_.address) res2: Address = null scala> me ? (_.address) ? (_.state) res3: String = null


Debido a que esto se vería terrible como un comentario, aquí hay una versión comentada del código de Walter:

/** * Safe dereference operator. E.g. ?(a.b.c.null.dd) */ def ?[A](block: => A) = { try { block } catch { // checks to see if the 3rd to last method called in the stack, is the ?() function, // which means the null pointer exception was actually due to a null object, // otherwise the ?() function would be further down the stack. case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => {null} // for any other NullPointerException, or otherwise, re-throw the exception. case e => throw e }

Y la especificación, que pasa:

case class Company(employee:Employee) case class Employee(address:Address){ def lookupAddressFromDb:Address = throw new NullPointerException("db error") } case class Address(city:String) "NullSafe operater" should { "return the leaf value when working with non-null tree" in { val company = Company(Employee(Address("Auckland"))) val result = ?( company.employee.address.city ) result mustEq "Auckland" } "return null when working with a null element at some point in the tree" in { val company = Company(null) val result = ?( company.employee.address.city ) result must beNull } "re-throw the NPE when working with a method which actually throws a NullPointerException" in { val company = Company(Employee(Address("Auckland"))) ?( company.employee.lookupAddressFromDb.city ) aka "the null-safe lookup method" must throwA[NullPointerException] } }


Hay dos cosas que deben considerarse aquí.

Primero, está el problema de la "nada". ¿Cómo encadenas cosas cuando una parte de la cadena puede no devolver nada? La respuesta es usar Option y for comprensiones. Por ejemplo:

scala> case class Address(city: Option[String] = None, street: Option[String] = None, number: Option[Int] = None) defined class Address scala> case class Contact(name: String, phone: Option[String] = None, address: Option[Address] = None) defined class Contact scala> case class ContactDetails(phone: Option[String] = None, address: Option[Address] = None) defined class ContactDetails scala> case class Contact(phone: Option[String] = None, address: Option[Address] = None) defined class Contact scala> case class Person(name: String, contactDetails: Option[Contact] = None) defined class Person scala> case class Company(name: String, contactPerson: Option[Person] = None) defined class Company scala> val p1 = Company("ABC", Some(Person("Dean", Some(Contact(None, Some(Address(city = Some("New England")))))))) p1: Company = Company(ABC,Some(Person(Dean,Some(Contact(None,Some(Address(Some(New England),None,None))))))) scala> val p2 = Company("Finnicky", Some(Person("Gimli", None))) p2: Company = Company(Finnicky,Some(Person(Gimli,None))) scala> for(company <- List(p1, p2); | contactPerson <- company.contactPerson; | contactDetails <- contactPerson.contactDetails; | address <- contactDetails.address; | city <- address.city) yield city res28: List[String] = List(New England)

Así es como se supone que debes escribir código que puede devolver algo o no en Scala.

El segundo problema, por supuesto, es que a veces puede que no tenga acceso al código fuente para hacer la conversión adecuada. En este caso, hay una sobrecarga de sintaxis adicional para ser cabeza, a menos que se pueda usar una implícita. Voy a dar un ejemplo a continuación, en el que utilizo una función " toOption ": existe tal cosa en Scala 2.8, de la que hablaré a continuación.

scala> def toOption[T](t: T): Option[T] = if (t == null) None else Some(t) toOption: [T](t: T)Option[T] scala> case class Address(city: String = null, street: String = null, number: Int = 0) defined class Address scala> case class Contact(phone: String = null, address: Address = null) defined class Contact scala> case class Person(name: String, contactDetails: Contact = null) defined class Person scala> case class Company(name: String, contactPerson: Person = null) defined class Company scala> val p1 = Company("ABC", Person("Dean", Contact(null, Address(city = "New England")))) p1: Company = Company(ABC,Person(Dean,Contact(null,Address(New England,null,0)))) scala> val p2 = Company("Finnicky", Person("Gimli")) p2: Company = Company(Finnicky,Person(Gimli,null)) scala> for(company <- List(p1, p2); | contactPerson <- toOption(company.contactPerson); | contactDetails <- toOption(contactPerson.contactDetails); | address <- toOption(contactDetails.address); | city <- toOption(address.city)) yield city res30: List[String] = List(New England)

Recuerde que puede ser bastante creativo al nombrar una función. Entonces, en lugar de " toOption ", podría haberlo llamado " ? ", En cuyo caso escribiría cosas como " ?(address.city) ".

Gracias a nuttycom por recordarme, en Scala 2.8 hay una Option fábrica en la Option objeto, así que puedo escribir la Option(something) . En efecto, puede reemplazar " toOption " arriba con " Option ". Y si prefieres usar ? , puedes usar import con cambiar nombre.


Me gustó el uso de Daniel C. Sobral para las comprensiones --- llega al punto más rápido que la cascada de match anidadas que había estado haciendo. Sin embargo, todavía no es muy conveniente porque todavía hay variables ficticias intermedias (y demasiada tipeo).

Queremos algo como a?.b?.c?.d así que no tenemos que pensar en lo que viene en el medio: simplemente trate de obtener algo y deme una Option en caso de que no pueda obtenerlo.

Por contexto, supongamos que tengo

case class Inner(z: Option[Int]) case class Outer(y: Option[Inner]) val x = Some(Outer(Some(Inner(Some(123)))))

que quiero desempaquetar El para la comprensión sería como el siguiente

for (tmp1 <- x; tmp2 <- tmp1.y; tmp3 <- tmp2.z) yield tmp3

que resulta en Some(123) . El problema son demasiadas variables temporales (y el hecho de que está leyendo parcialmente hacia atrás).

Me resulta más fácil hacerlo con flatMap , como este

x.flatMap(_.y.flatMap(_.z))

o

x flatMap {_.y flatMap {_.z}}

que también resulta en Some(123) .

Uno podría reducir la verbosidad y usar el deseado ? símbolo dando efectivamente a la Option escriba un método ? eso hace lo mismo que flatMap . Option está sellada de subclases, pero podemos simular el nuevo método con conversiones implícitas.

case class OptionWrapper[A](opt: Option[A]) { def ?[B](f: (A) => Option[B]): Option[B] = opt.flatMap(f) } implicit def toOptionWrapper[T](opt: Option[T]) = OptionWrapper(opt) implicit def fromOptionWrapper[T](wrap: OptionWrapper[T]) = wrap.opt

Y entonces

x ? {_.y ? {_.z}}

produce Some(123 Todavía no es perfecto porque hay paréntesis anidados y guiones bajos que hay que acertar, pero es mejor que cualquier otra alternativa que he visto.


Monadic bind (flatMap / map) con el tipo scala.Option. El apoyo también es proporcionado por for-comprehensions. Scalaz proporciona un estilo de funcionador aplicativo si lo prefiere.

Esto no es equivalente, pero es una solución mucho mejor que el operador de Groovy por muchas razones.


No es mío sino de un compañero de trabajo

class NullCoalescer[T <: AnyRef](target: T) { def ?? (other: T) = if(target == null) other else target } object NullCoalescerConversions { implicit def toNullCoalescer[T <: AnyRef](target: T): NullCoalescer[T] = new NullCoalescer(target) } println (System.getProperty("maybe") ?? "definitely")


Para seguir la respuesta de Daniel C. Sobral, la razón por la que se prefiere la opción es porque Scala idiomático no usa punteros nulos. Si puede, vuelva a escribir el código para devolver las Opciones en lugar de las referencias que aceptan nulos. Los mapas planos encadenados son más limpios que las comprensiones, ya que no necesita un nuevo nombre de variable para cada paso. Si todos los valores son opcionales (como en el ejemplo de Groovy), el enfoque de Scala se vería así:

(company flatMap _.getContactPerson flatMap _.getContactDetails flatMap _.getAddress flatMap _.getCity) match { case Some(city) => ... case None => ... }

Si debe usar valores anulables para la interoperabilidad de Java, este es un enfoque que le brinda seguridad sin disputas NPE o demasiado desorden:

sealed trait Nullable[+A] { def apply[B](f:A=>B): Nullable[B] } def ?[A](a: A) = a match { case null => NullRef case _ => Ref(a) } case class Ref[A](value: A) extends Nullable[A] { def apply[B](f:A=>B) = ?(f(value)) } object NullRef extends Nullable[Nothing] { def apply[B](f: Nothing=>B): Nullable[B] = NullRef } ?(company)(_.getContactPerson)(_.getContactDetails)(_.getAddress)(_.getCity) match { case Ref(city) => ... case _ => ... }

Esto debería ser fácil de expandir a una mónada de estilo Opción completa si así lo desea.