Domanda

Spesso affronto la seguente situazione: supponiamo di avere queste tre funzioni

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

E anche io ho calculate funzione. Il mio primo approccio può apparire così:

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

Sembra bellissimo e senza parentesi ricci - solo un'espressione. Ma non è ottimale, quindi finisco con questo codice:

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

  thirdFn(first, second, second + a)
}

Ora sono diverse espressioni circondate da parentesi ricci. In tali momenti invidio un po 'Clojure. Insieme a Lascia la funzione Posso definire questa funzione in un'unica espressione.

Quindi il mio obiettivo qui è definire calculate funzione con un'espressione. Mi viene in mente 2 soluzioni.

1 - con Scalaz Posso definirlo in questo modo (ci sono modi migliori per farlo con Seraz?):

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

Quello che non mi piace di questa soluzione è che è nidificato. Più valS ho più profondo che questo nidificazione è.

2 - con for Comprensione posso ottenere qualcosa di simile:

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

Da una mano questa soluzione ha una struttura piatta, proprio come let In Clojure, ma dall'altra parte ho bisogno di avvolgere le funzioni. Option e ricevere Option come risultato da calculate (Va bene, ho a che fare con Nulls, ma non ... e non voglio).

Ci sono modi migliori per raggiungere il mio obiettivo? Qual è il modo idiomatico per affrontare tali situazioni (potrei essere con cui dovrei restare con vals ... ma let Il modo di farlo sembra così elegante)?

D'altra parte è collegato a Trasparenza referenziale. Tutte e tre le funzioni sono referenzialmente trasparenti (nel mio esempio firstFn Calcola qualche costante come PI), quindi teoricamente possono essere sostituiti con i risultati di calcolo. Lo so, ma il compilatore no, quindi non può ottimizzare il mio primo tentativo. Ed ecco la mia seconda domanda:

Posso in qualche modo (posso essere con l'annotazione) dare un suggerimento al compilatore, che la mia funzione è referenzialmente trasparente, in modo che possa ottimizzare questa funzione per me (ad esempio mettere una sorta di memorizzazione nella cache)?

Modificare

Grazie a tutti per le fantastiche risposte! È semplicemente impossibile selezionare una risposta migliore (può essere perché sono tutti così buoni), quindi accetterò la risposta con i voti più aggiornati, penso che sia abbastanza giusto.

È stato utile?

Soluzione

Nel caso non ricorsivo, let è una ristrutturazione di 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)}}

Certo, è ancora nidificato. Non posso evitarlo. Ma hai detto che ti piace la forma monadica. Quindi ecco la monade dell'identità

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

Ed ecco il tuo calcolo monadico. Scartare il risultato chiamando .x

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

Ma a questo punto sembra sicuramente la versione Val originale.

L'identità Monad esiste in Scalaz con più raffinatezza

http://scalaz.googlecode.com/svn/continuou/latest/browse.sxr/scalaz/idenza.scala.html

Altri suggerimenti

Resta con la forma originale:

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

  thirdFn(first, second, second + a)
}

È conciso e chiaro, anche per gli sviluppatori di Java. È approssimativamente equivalente a lasciare, proprio senza limitare l'ambito dei nomi.

Ecco un'opzione che potresti aver trascurato.

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

Se vuoi effettivamente creare un metodo, questo è il modo in cui lo farei.

In alternativa, potresti creare un metodo (si potrebbe nominarlo let) che evita di nidificare:

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))

Ma ora potresti dover nominare le stesse cose più volte.

Se ritieni che la prima forma sia più pulita/più elegante/più leggibile, allora perché non attenersi?

Innanzitutto, leggi Questo recente messaggio di commit al compilatore Scala da nientemeno che Martin Odersky e prendilo a cuore ...


Forse il vero problema qui è saltare immediatamente la pistola sostenendo che non è ottimale. Il JVM è piuttosto caldo nell'ottimizzare questo genere di cose. A volte è semplicemente fantastico!

Supponendo che tu abbia un problema di prestazioni autentico in un'applicazione che ha una vera necessità di una velocità, dovresti iniziare con un rapporto Profiler dimostrazione Che si tratta di un collo di bottiglia significativo, su un JVM adeguatamente configurato e riscaldato.

Quindi, e solo allora, dovresti guardare modi per renderlo più veloce che potrebbe finire per sacrificare la chiarezza del codice.

Perché non usare la corrispondenza dei pattern qui:

def calcola (a: long) = firstfn match {case f => secondfn (f) match {case s => terzofn (f, s, s + a)}}

Che ne dici di usare Currying per registrare i valori di restituzione della funzione (i parametri dei gruppi di parametri precedenti sono disponibili in gruppi di superamento).

Un po 'strano ma abbastanza conciso e nessuna ripetuta invocazioni:

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

println(calculate(1L)()())
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top