Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Punti dati

Dati, incontrare il mio nuovo amico, F #

Julie Lerman

Scaricare il codice di esempio

Julie LermanHo avuto qualche esposizione alla programmazione funzionale negli ultimi anni. Alcune di queste era implicito: Codifica con lambda in LINQ è la programmazione funzionale. Alcuni è stata esplicita: Utilizzando Entity Framework e LINQ to SQL CompiledQuery tipo mi ha costretto a utilizzare il delegato Func .NET. Che è stato sempre un po ' complicata perché l'ho fatto così di rado. Sono stata esposta anche alla programmazione funzionale attraverso l'entusiasmo contagioso di Rachel Reese, un Microsoft MVP, che non solo partecipa al mio gruppo di utenti locale (VTdotNET), ma gestisce anche il gruppo di VTFun qui in Vermont, che si concentra su molti aspetti e linguaggi di programmazione funzionale. La prima volta che sono andato a una riunione di VTFun, era pieno di matematici e scienziati di razzo. Non sto scherzando. Uno dei regolari è un teorico della materia condensata presso Università del Vermont. Svenire! Ero un po ' travolti da discussioni ad alto livello, ma è stato divertente sentire realmente come il manichino nella stanza. La maggior parte di ciò che è stato detto è andato a destra sopra la mia testa — ad eccezione di una singola istruzione che ha attirato la mia attenzione: "Linguaggi funzionali non servono nessun stinkin ' cicli foreach." -Ehi! Volevo sapere quello che significava. Perché non hanno bisogno di cicli foreach?

Quello che di solito ho sentito è che la programmazione funzionale è ideale per operazioni matematiche. Non essendo un geek di matematica, interpretato che significa "per le demo che utilizzano la sequenza di Fibonacci per testare un comportamento asincrono" e non ha prestato molta più attenzione. Questo è il problema con udito solo un passo breve ascensore sulla programmazione funzionale.

Ma finalmente ho iniziato a sentire una più precisa caratterizzazione — che la programmazione funzionale è grande per la scienza di dati. Che certamente fa appello a un geek di dati. F #, il linguaggio funzionale di Microsoft .NET Framework, gli sviluppatori .NET porta tutti i tipi di funzionalità di scienza di dati. Ha librerie interi dedicato alla creazione di grafici, manipolazione di tempo e le operazioni di impostazione. Ha le API con logica dedicato alle matrici D 2D, 3D e 4. Esso comprende l'unità di misura ed è in grado di vincolare e convalidare basato su unità specificata.

F # consente inoltre interessanti scenari di codificazione in Visual Studio. Piuttosto che costruire la logica nei file di codice e poi il debug, è possibile scrivere ed eseguire il codice riga per riga in una finestra interattiva e quindi spostare il codice di successo in un file di classe. È possibile ottenere una sensazione di grande per F # come lingua dell'articolo di Tomas Petricek, "capire il mondo con F #" (bit.ly/1cx3cGx). Con l'aggiunta di F # nel toolset .NET, Visual Studio diventa un potente strumento per la creazione di applicazioni che eseguono la logica di scienza di dati.

In questo articolo, mi concentrerò su un aspetto della programmazione funzionale che sono venuto a capire dal sentito quel commento sui non necessitano di cicli foreach e F #. Linguaggi funzionali sono davvero bravi a lavorare con insiemi di dati. In un linguaggio procedurale, quando si lavora con set, devi esplicitamente scorrerli per eseguire la logica. Un linguaggio funzionale, al contrario, comprende set su un livello diverso, quindi devi solo chiedere per eseguire una funzione su un insieme, piuttosto che scorrere l'insieme e svolgono una funzione su ogni elemento. E che la funzione può essere definita con un sacco di logica, tra cui matematica, se necessario.

LINQ fornisce scorciatoie per questo, anche un metodo ForEach in cui è possibile passare una funzione. Ma sullo sfondo, la lingua (forse C# o Visual Basic) questo traduce semplicemente in un ciclo.

Un linguaggio funzionale come F # ha la capacità di svolgere la funzione impostata a un livello inferiore ed è molto più veloce grazie alla sua facile elaborazione parallela. Aggiungere a questo altri vantaggi, quali una ricca capacità di elaborazione matematica e un sistema di tipizzazione incredibilmente dettagliato che comprende anche unità di misura, e hai un potente strumento per l'esecuzione di calcoli di grandi serie di dati.

C'è molto altro da F # e altri linguaggi di programmazione funzionale che è ancora lontano oltre la mia portata. Ma quello che voglio fare in questa colonna è concentrarsi su un modo di beneficiare rapidamente un aspetto particolare di linguaggi funzionali senza fare un investimento enorme: nella mia applicazione in movimento logica dal database. Questo è il modo che mi piace imparare: trovare un paio di cose posso capire e usare per scattare qualche bambino passi in una nuova piattaforma, linguaggio, framework o altro tipo di strumento.

Ho sentito Reese tenta di spiegare chiaramente agli sviluppatori che utilizzando F # non significa commutazione linguaggi di sviluppo. Nello stesso modo si potrebbe usare una query LINQ o una stored procedure per risolvere un problema particolare, è possibile creare una libreria di F # metodi per risolvere i tipi di problemi nella tua app che linguaggi funzionali sono davvero bravi a.

Mi concentrerò su qui è estrarre la logica di business che è stato costruito nel mio database, logica per la gestione di grandi insiemi di dati — qualcosa a cui il database è eccellente — e la sua sostituzione con metodi funzionali.

E poiché F # è stato progettato per lavorare con insiemi e davvero intelligente con funzioni matematiche, il codice può essere più efficiente di quanto potrebbe essere in SQL o procedurali linguaggi come c# o Visual Basic. È così facile avere F # eseguire la logica gli elementi del set in parallelo. Non solo questo può ridurre la quantità di codice sarebbe probabilmente necessario in un linguaggio procedurale per emulare questo comportamento, la parallelizzazione significa il codice verrà eseguito molto più velocemente. Si potrebbe progettare il codice c# per eseguire in parallelo, ma sarebbe piuttosto non andare attraverso quello sforzo, neanche.

Un problema reale

Molti anni fa, ho scritto un Visual Basic 5 app che dovevano raccogliere, conservare e segnalare un sacco di dati scientifici ed eseguire un sacco di calcoli. Alcuni di questi calcoli sono stati così complessi che li mandai a un'API di Excel.

Uno dei calcoli coinvolti determinando le libbre per pollice quadrato (PSI), basato sulla quantità di peso che ha causato un blocco di materiale da rompere. Il blocco potrebbe essere uno qualsiasi di un certo numero di forme cilindriche e dimensioni. L'app sarebbe utilizzare le misure del cilindro e, a seconda della sua forma e dimensione, una formula specifica per calcolare la sua area. Sarebbe quindi applicare un fattore di tolleranza rilevanti e, infine, la quantità di peso ci sono voluti per rompere il cilindro. Tutto questo insieme fornito il PSI per il particolare materiale testato.

Nel 1997, sfruttando le API di Excel per valutare la formula all'interno di Visual Basic 5 e Visual Basic 6 sentivo come una soluzione abbastanza intelligente.

Muovendo la Eval

Anni più tardi, rinnovato l'applicazione in .NET. A quel tempo, ha deciso di sfruttare la potenza di SQL Server per eseguire il calcolo del PSI di grandi serie di cilindri, dopo che sono stati aggiornati da un utente, piuttosto che avere il computer dell'utente spendere tempo tutti quei calcoli. Che ha funzionato abbastanza bene.

Più anni passati e cambiato mie idee sulla logica di business nel database. Volevo tirare tale calcolo al lato client e, naturalmente, le macchine client erano più veloci di allora, comunque. Non era troppo difficile riscrivere la logica in c#. Dopo che un utente aggiornati una serie di cilindri con il peso che ha preso a romperle (carico, rappresentato in sterline), l'app vuoi scorrere i cilindri aggiornati e calcolare il PSI. Quindi potrei aggiornare cilindri nel database con i loro nuovi carichi e valori PSI.

Per motivi di confronto familiare c# per l'esito finale in F # (che vedrete poco), ho fornito la quotazione del tipo cilindro, CylinderMeasurements, in Figura 1 e la mia classe c# calcolatrice in Figura 2, così si può vedere come io derivano le psi per una serie di cilindri. È il metodo di CylinderCalculator.UpdateCylinders che viene chiamato per avviare il calcolo del PSI per una serie di cilindri. Scorre ogni cilindro nel set ed esegue i calcoli appropriati. Si noti che uno dei metodi, GetAreaForCalculation, dipende il tipo di cilindro perché calcolare l'area del cilindro utilizzando la formula appropriata.

Figura 1 classe CylinderMeasurement

public class CylinderMeasurement
{
  public CylinderMeasurement(double widthA, double widthB,
    double height)
  {
    WidthA = widthA;
    WidthB = widthB;
    Height = height;
  }
  public int Id { get; private set; }
  public double Height { get; private set; }
  public double WidthB { get; private set; }
  public double WidthA { get; private set; }
  public int LoadPounds { get; private set; }
  public double Psi { get; set; }
  public CylinderType CylinderType { get; set; }
  public void UpdateLoadPoundsAndTypeEnum(int load, 
    CylinderType cylType) {
    LoadPounds = load; CylinderType = cylType;
  }
   private double?
Ratio {
    get {
      if (Height > 0 && WidthA + WidthB > 0) {
        return Math.Round(Height / ((WidthA + WidthB) / 2), 2);
      }
      return null;
    }
  }
  public double ToleranceFactor {
    get {
      if (Ratio > 1.94 || Ratio < 1) {
        return 1;
      }
      return .979;
    }
  }
}

Figura 2 classe Calculator per calcolare PSI

public static class CylinderCalculator
  {
    private static CylinderMeasurement _currentCyl;
    public static void UpdateCylinders(IEnumerable<CylinderMeasurement> cyls) {
      foreach (var cyl in cyls)
      {
        _currentCyl = cyl;
        cyl.Psi = GetPsi();
      }
    }
    private static double GetPsi() {
      var area = GetAreaForCylinder();
      return PsiCalculator(area);
    }
    private static double GetAreaForCylinder() {
      switch (_currentCyl.CylinderType)
      {
        case CylinderType.FourFourEightCylinder:
          return 3.14159*((_currentCyl.WidthA + _currentCyl.WidthB)/2)/2*
            ((_currentCyl.WidthA + _currentCyl.WidthB)/2/2);
        case CylinderType.SixSixTwelveCylinder:
          return 3.14159*((_currentCyl.WidthA + _currentCyl.WidthB)/2)/2*
            ((_currentCyl.WidthA + _currentCyl.WidthB)/2/2);
        case CylinderType.ThreeThreeSixCylinder:
          return _currentCyl.WidthA*_currentCyl.WidthB;
        case CylinderType.TwoTwoTwoCylinder:
          return ((_currentCyl.WidthA + _currentCyl.WidthB)/2)*
            ((_currentCyl.WidthA + _currentCyl.WidthB)/2);
        default:
          throw new ArgumentOutOfRangeException();
      }
    }
    private static int PsiCalculator(double area) {
      if (_currentCyl.LoadPounds > 0 && area > 0)
      {
        return (int) (Math.Round(_currentCyl.LoadPounds/area/1000*
          _currentCyl.ToleranceFactor, 2)*1000);
      }
      return 0;
    }
  }

Focus di dati e di elaborazione più veloce con F #

Infine, ho scoperto che F #, grazie alla sua naturale inclinazione per la modifica dei dati, fornisce una soluzione molto migliore di valutazione uno formula alla volta.

Alla sessione introduttiva su F # data da Reese, ho spiegato questo problema, che era stato fastidioso a me per così tanti anni e ha chiesto se un linguaggio funzionale potrebbe essere utilizzato per risolverlo in modo più soddisfacente. Lei ha confermato che potevo applicare la mia logica di calcolo completo su un set completo e lasciare che F # derivano le psi per molti cilindri in parallelo. Allo stesso tempo, ho potuto ottenere la funzionalità lato client e un incremento delle prestazioni.

La chiave per me è stato rendersi conto che potrei usare F # per risolvere un problema particolare più o meno allo stesso modo utilizzare una stored procedure, è semplicemente un altro strumento nella mia cintura strumento. Non necessita di rinunciare al mio investimento in c#. Forse ci sono alcuni che sono proprio l'opposto, scrivendo la maggior parte delle loro applicazioni in F # e utilizza c# per attaccare i problemi particolari. In ogni caso, utilizzando il linguaggio c# CylinderCalculator come guida, Reese ha creato un piccolo progetto F # che ha il compito e sono stato in grado di sostituire una chiamata al mio calcolatore con una chiamata alla sua nel mio test, come mostrato Figura 3.

Figura 3 la calcolatrice PSI F #

module calcPsi =
  let fourFourEightFormula WA WB = 3.14159*((WA+WB)/2.)/2.*((WA+WB)/2./2.)
  let sixSixTwelveFormula WA WB = 3.14159*((WA+WB)/2.)/2.*((WA+WB)/2./2.)
  let threeThreeSixFormula (WA:float) (WB:float) = WA*WB
  let twoTwoTwoFormula WA WB = ((WA+WB)/2.)*((WA+WB)/2.)
  // Ratio function
  let ratioFormula height widthA widthB =
    if (height > 0.
&& (widthA + widthB > 0.)) then
      Some(Math.Round(height / ((widthA + widthB)/2.), 2))
    else
      None
  // Tolerance function
  let tolerance (ratioValue:float option) = match ratioValue with
    | _ when (ratioValue.IsSome && ratioValue.Value > 1.94) -> 1.
| _ when (ratioValue.IsSome && ratioValue.Value < 1.) -> 1.
| _ -> 0.979
  // Update the PSI, and return the original cylinder information.
let calculatePsi (cyl:CylinderMeasurement) =
    let formula = match cyl.CylinderType with
      | CylinderType.FourFourEightCylinder -> fourFourEightFormula
      | CylinderType.SixSixTwelveCylinder -> sixSixTwelveFormula
      | CylinderType.ThreeThreeSixCylinder -> threeThreeSixFormula
      | CylinderType.TwoTwoTwoCylinder -> twoTwoTwoFormula
      | _ -> failwith "Unknown cylinder"
    let basearea = formula cyl.WidthA cyl.WidthB
    let ratio = ratioFormula cyl.Height cylinder.WidthA cyl.WidthB
    let tFactor = tolerance ratio
    let PSI = Math.Round((float)cyl.LoadPounds/basearea/1000.
* tFactor, 2)*1000.
cyl.Psi <- PSI
    cyl
  // Map evaluate to handle all given cylinders.
let getPsi (cylinders:CylinderMeasurement[])
              = Array.Parallel.map calculatePsi cylinders

Se, come me, siete nuovi in F #, si potrebbe guardare solo la quantità di codice e non vedere nessun punto nella scelta di questo itinerario sopra c#. Dopo un esame più attento, tuttavia, si può apprezzare l'asciuttezza del linguaggio, la capacità di definire le formule più elegantemente e, alla fine, il modo semplice, che posso applicare la funzione di calculatePsi che Reese definito nella matrice dei cilindri che sono passato al metodo.

La concisione è grazie al fatto che F # è meglio progettato per eseguire funzioni matematiche di C# è, così la definizione di tali funzioni è più efficiente. Ma oltre l'appello di geek del linguaggio, ero interessato a prestazioni. Quando aumentato il numero di cilindri per ogni set nel mio test, inizialmente non ho visto un miglioramento delle prestazioni sopra c#. Reese ha spiegato che l'ambiente di test è più costoso quando si utilizza F #. Così testato quindi le prestazioni in un'applicazione di console utilizzando il cronometro per segnalare il tempo passato. L'app costruito una lista di 50.000 cilindri, avviato il cronometro, passato i cilindri a c# o F # calcolatrice per aggiornare il valore PSI per ogni cilindro, poi fermato il cronometro quando i calcoli erano completi.

Nella maggior parte dei casi, il processo di c# ha preso circa tre volte più lungo il processo di F #, anche se vuoi battere circa il 20 per cento del tempo C# F # con un piccolo margine. Io non riesco a spiegare la stranezza, ma è possibile che non c'è più che ho bisogno di capire per eseguire la profilatura più vera.

Con un occhio fuori per logica che implora per un linguaggio funzionale

Così mentre io devo lavorare alle mie abilità di F #, mia nuova comprensione sta per applicare bene apps che ho già in produzione, come pure applicazioni future. Nelle mie applicazioni di produzione, posso guardare logica aziendale hai relegato al database e considerare se un app trarrebbero beneficio da una sostituzione di F #. Con nuove applicazioni, ora ho un occhio più acuto per funzionalità individuandone che i posso codice in modo più efficiente con F #, effettuazione di manipolazione dei dati, sfruttando fortemente tipizzato unità di misura e guadagnando prestazioni. E c'è sempre il divertimento di imparare una nuova lingua e trovare solo i giusti scenari per i quali è stato costruito!

Julie Lerman è un Microsoft MVP, mentore di .NET e consulente che vive nelle colline del Vermont. È possibile trovare la sua presentazione sull'accesso ai dati e altri argomenti di Microsoft .NET a gruppi di utenti e conferenze in tutto il mondo. Blog di lei a /Blog ed è l'autore di "programmazione Entity Framework" (2010) così come un codice prima edizione (2011) e un DbContext edizione (2012), tutti da o ' Reilly Media. Seguirla su Twitter a Twitter.com /julielerman. e vedere i suoi corsi Pluralsight presso Juliel.me/PS-video.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Rachel Reese (logica di Firefly)
Rachel Reese è un geek di matematica e ingegnere software da lungo tempo in Nashville, TN. Fino a poco tempo, lei correva il Burlington, VT funzionale programmazione user group, @VTFun, che era una costante fonte di ispirazione per lei, e al quale ha spesso parlato su F #. Lei è anche un ASPInsider, un MVP F #, un appassionato di comunità, uno della Fondazione @lambdaladiese una Claudia. Si può trovarla su twitter, @rachelreese, o sul suo blog: rachelree.se.