Ridurre i vars nei programmi Scala

5

Ho studiato scala per la scorsa settimana circa e le ideologie ad essa associate e la programmazione funzionale in generale. Come previsto, il passaggio dalla programmazione imperativa a quella funzionale non è così facile come speravo. Ad esempio, ho convertito un esercizio di programmazione che ho fatto in Java in Scala. Quello che ho scoperto è che per me è quasi impossibile passare da variabili mutabili allo stile Scala di valori immutabili.

Per le persone TL; DR, la mia domanda principale è: come si diminuisce l'utilizzo di var in un programma funzionale? Per motivi di utilizzo, fornirò del codice di esempio:

import scala.io._
object Bowling {
  def main(args: Array[String]): Unit = {
    val input = Source.fromFile("bowling.in").getLines
    for(testCase <- input) {
      val numberOfRows = augmentString(testCase).toInt
      var index = 0
      var a = 1
      var sum = 0
      while(index < numberOfRows) {
        sum += a
        a += 4
        index += 1
      }
      println(sum)
    }
  }
}

EDIT: Accetto la risposta di Karl per la sua concisione, anche se apprezzo molto l'esempio di codice di Ptharien e la spiegazione di Doval dei concetti funzionali nel suo insieme.

    
posta Keith Mattix 10.05.2014 - 05:39
fonte

3 risposte

4

L'approccio generale è invece di mutare una variabile in un ciclo, inserire i valori in raccolte ed eseguire una serie di operazioni su tali raccolte. Nota che la maggior parte delle volte, gli algoritmi funzionali non hanno bisogno di variabili indice. Cerca relazioni matematiche che ti consentano di tenerne conto. Ad esempio:

val sums = for(testCase <- input) yield {
  val numberOfRows = testCase.toInt
  val a = 1 until numberOfRows * 4 by 4
  a.sum
}
sums foreach println

Solo perché è una serie di operazioni su una collezione non significa che devi raggruppare tutte le operazioni una dopo l'altra. Non dimenticare di suddividere le linee, denominare i risultati intermedi e utilizzare le funzioni nominate anziché lambda, se necessario. Il compilatore essenzialmente ottimizza numberOfRows e a , ma inserendoli lì aggiunge molta leggibilità per gli umani.

Non evitare di comprendere solo per la loro somiglianza superficiale con i loop mutati. Sono spesso la chiave per una migliore leggibilità.

Nota ho spostato println all'esterno della comprensione per. Isolare gli effetti collaterali di questo tipo può spesso rendere più facile vedere una soluzione funzionale pura per il resto del problema.

Inoltre, per inciso, non hai bisogno del augmentString . È implicitamente aggiunto per te.

    
risposta data 10.05.2014 - 19:45
fonte
7

Ci sono un paio di approfondimenti che faciliteranno la tua transizione dalla programmazione imperativa alla programmazione funzionale:

  1. La mutazione di un certo valore x può essere sostituita con la creazione di una copia modificata di x . Ci sono versioni persistenti della maggior parte delle strutture di dati che possono farlo efficientemente copiando solo le parti della struttura che sono cambiate e condividendo il resto.

  2. I loop possono essere sostituiti con funzioni ricorsive. Nelle lingue con l'ottimizzazione chiamata a coda , la ricorsione della coda non farà esplodere lo stack perché il compilatore può riscriverlo come un ciclo sotto il cofano.

  3. Una funzione che dipende da una variabile libera (cioè una variabile che non è un parametro formale della funzione) può essere convertita in una funzione che prende come argomento il valore corrente di tale variabile.

Possiamo combinare tutte e 3 le regole per riscrivere funzionalmente i loop imperiali. Desugar for loops in while loop, converte il loop in una funzione ricorsiva, converte le variabili di iterazione in argomenti di funzione. Quindi questo:

public void printFirst(int n) {
    for (int i = 0; i < 10; i++) {
        println(i);
    }
}

può essere convertito in questo:

def recursionHelper(i : int, max : int) {
    if (i == max) {
        return;
    } else {
        print(i);
        // Instead of mutating i, create a new value
        recursionHelper(i + 1, max);
    }
}

def printFirst(n : int) {
    recursionHelper(0, n);
}

Tali traduzioni possono essere eseguite in modo abbastanza meccanico. Probabilmente il codice risultante non sarà idiomatico, dal momento che la versione imperativa del codice probabilmente non ha fatto molto uso di funzioni di alto ordine (funzioni che assumono altre funzioni come argomenti) e espressioni lambda (una sintassi concisa per la creazione di funzioni anonime) , ma è un buon primo passo per imparare a pensare in modo funzionale.

    
risposta data 10.05.2014 - 20:26
fonte
4

Una conversione immediata del tuo codice di esempio in " var -less", ma non ancora completo, lo stile sarebbe il seguente:

import scala.io._
object Bowling {
  def main(args: Array[String]): Unit = {
    val input = Source.fromFile("bowling.in").getLines
    for(testCase <- input) {
      val numberOfRows = augmentString(testCase).toInt
      val (_, sum) = 1 to numberOfRows foldr ((1, 0)) { (_, v) => v match {
        case (a, sum) => (a + 4, sum + a)
      } }
      println(sum)
    }
  }
}

Realizzare una versione più funzionale sarebbe una trasformazione molto più ampia e probabilmente dipenderà esattamente da quale sia il punto del programma . Metterò la risposta così com'è, e lavorerò su una modifica che, si spera, dimostri questo refactoring più completo.

EDIT: Ecco come scriverei questo programma più generalmente funzionalmente (rimanendo comunque entro i confini della libreria standard di Scala):

import scala.io._
object Bowling extends App {
  Source.fromFile("bowling.in").getLines map { _.toInt } map { testCase =>
    1 to testCase foldr ((1, 0)) { (_, v) => v match {
      case (a, sum) => (a + 4, sum + a)
    } }
  } map { _._2 } foreach { println _ }
}

Non preoccuparti di capire come farlo tutto in una volta; ci vuole pratica e una settimana o due non è neanche lontanamente abbastanza per capire un paradigma di programmazione completamente diverso da quello a cui sei abituato. Aspettatevi diversi mesi prima che sembriate di comprendere le basi della programmazione funzionale, sebbene sia possibile scrivere Scala nello stile "Java meno virgola e punto e virgola" nel frattempo. :)

    
risposta data 10.05.2014 - 06:44
fonte

Leggi altre domande sui tag