Pregunta

A menudo enfrento la siguiente situación: supongo que tengo estas tres funciones

def firstFn: Int = ...
def secondFn(b: Int): Long = ...
def thirdFn(x: Int, y: Long, z: Long): Long = ...

Y también tengo calculate función. Mi primer enfoque puede verse así:

def calculate(a: Long) = thirdFn(firstFn, secondFn(firstFn), secondFn(firstFn) + a)

Se ve hermoso y sin corchetes, solo una expresión. Pero no es óptimo, así que termino con este código:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

Ahora son varias expresiones rodeadas de soportes rizados. En tales momentos, envidio un poco a Clojure. Con Deje que la función Puedo definir esta función en una expresión.

Entonces mi objetivo aquí es definir calculate función con una expresión. Se me ocurren 2 soluciones.

1 - con escala Puedo definirlo así (¿hay mejores formas de hacer esto con Scalaz?):

  def calculate(a: Long) = 
    firstFn |> {first => secondFn(first) |> {second => thirdFn(first, second, second + a)}}

Lo que no me gusta de esta solución es que está anidado. Cuanto más valS tengo más profundo que esta anidación es.

2 - con for comprensión puedo lograr algo similar:

  def calculate(a: Long) = 
    for (first <- Option(firstFn); second <- Option(secondFn(first))) yield thirdFn(first, second, second + a)

Desde un lado, esta solución tiene una estructura plana, al igual que let en Clojure, pero por otro lado necesito envolver las funciones de los resultados en Option y recibir Option como resultado de calculate (Es bueno que estoy tratando con nulos, pero no ... y no quiero).

¿Hay mejores formas de lograr mi objetivo? ¿Cuál es la forma idiomática para lidiar con tales situaciones (puede ser que debería quedarme con valS ... pero let ¿La forma de hacerlo se ve tan elegante)?

De otra mano está conectado a Transparencia referencial. Las tres funciones son referencialmente transparentes (en mi ejemplo firstFn Calcula un poco de constante como Pi), por lo que en teoría se pueden reemplazar con resultados de cálculo. Sé esto, pero el compilador no lo hace, por lo que no puede optimizar mi primer intento. Y aquí está mi segunda pregunta:

¿Puedo de alguna manera (puede estar con anotación) dar sugerencia al compilador, que mi función es referencialmente transparente, para que pueda optimizar esta función para mí (poner algún tipo de almacenamiento en caché allí, por ejemplo)?

Editar

¡Gracias a todos por las grandes respuestas! Es simplemente imposible seleccionar una mejor respuesta (puede ser porque todos son muy buenos), así que aceptaré la respuesta con la mayor cantidad de votos, creo que es bastante justo.

¿Fue útil?

Solución

En el caso no recursivo, Sea una reestructuración de Lambda.

def firstFn : Int = 42
def secondFn(b : Int) : Long = 42
def thirdFn(x : Int, y : Long, z : Long) : Long = x + y + z

def let[A, B](x : A)(f : A => B) : B = f(x)

def calculate(a: Long) = let(firstFn){first => let(secondFn(first)){second => thirdFn(first, second, second + a)}}

Por supuesto, eso todavía está anidado. No puedo evitar eso. Pero dijiste que te gusta la forma monádica. Así que aquí está la identidad monad

case class Identity[A](x : A) {
   def map[B](f : A => B) = Identity(f(x))
   def flatMap[B](f : A => Identity[B]) = f(x)
}

Y aquí está tu calculación monádica. Descompensar el resultado llamando a .x

def calculateMonad(a : Long) = for {
   first <- Identity(firstFn)
   second <- Identity(secondFn(first))
} yield thirdFn(first, second, second + a)

Pero en este punto, seguro que se parece a la versión original de Val.

La mónada de identidad existe en Scalaz con más sofisticación

http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/ididentity.scala.html

Otros consejos

Quédate con la forma original:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

Es conciso y claro, incluso para los desarrolladores de Java. Es más o menos equivalente de dejar, solo sin limitar el alcance de los nombres.

Aquí hay una opción que puede haber pasado por alto.

def calculate(a: Long)(i: Int = firstFn)(j: Long = secondFn(i)) = thirdFn(i,j,j+a)

Si realmente quieres crear un método, esta es la forma en que lo haría.

Alternativamente, puede crear un método (uno puede nombrarlo let) que evita la anidación:

class Usable[A](a: A) {
  def use[B](f: A=>B) = f(a)
  def reuse[B,C](f: A=>B)(g: (A,B)=>C) = g(a,f(a))
  // Could add more
}
implicit def use_anything[A](a: A) = new Usable(a)

def calculate(a: Long) =
  firstFn.reuse(secondFn)((first, second) => thirdFn(first,second,second+a))

Pero ahora es posible que necesite nombrar las mismas cosas varias veces.

Si siente que la primera forma es más limpia/más elegante/más legible, ¿por qué no simplemente quedarse con ella?

Primero, lea Este reciente mensaje de confirmación al compilador de Scala desde nada menos que Martin Odersky y llévelo en serio ...


Quizás el verdadero problema aquí es saltar instantáneamente el arma al afirmar que es subóptimo. El JVM está bastante caliente para optimizar este tipo de cosas. A veces, ¡es simplemente increíble!

Suponiendo que tenga un problema de rendimiento genuino en una aplicación que necesita una velocidad genuina de una velocidad, debe comenzar con un informe de perfilador prueba que este es un cuello de botella significativo, en un JVM adecuadamente configurado y calentado.

Entonces, y solo entonces, si busca formas de hacerlo más rápido, lo que puede terminar sacrificando la claridad del código.

¿Por qué no usar la coincidencia de patrones aquí?

def calculación (a: long) = firstfn Match {case F => Secondfn (f) Match {Case S => Thirdfn (F, S, S + A)}}

¿Qué tal usar el curry para registrar los valores de retorno de la función (los parámetros de los grupos de parámetros anteriores están disponibles en grupos de superación).

Un poco de aspecto extraño pero bastante conciso y sin invocaciones repetidas:

def calculate(a: Long)(f: Int = firstFn)(s: Long = secondFn(f)) = thirdFn(f, s, s + a)

println(calculate(1L)()())
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top