2009-12-13 14 views
12

Mi sono dilettato con F # in Visual Studio 2010. Sono uno sviluppatore con più esperienza di progettazione di codice/architettura in linguaggi orientati agli oggetti come C# e Java.Coding Practice for F #

Per espandere il mio set di competenze e aiutare a prendere decisioni migliori sto provando diverse lingue per fare cose diverse. In particolare, prendi il codice della codifica "correttamente" usando i linguaggi funzionali (in questo caso F #).

Un semplice esempio sta generando alcuni XML, quindi aggiungendo alcuni filtri per eliminare alcuni elementi.

Ecco il mio codice:

open System 
open System.Xml.Linq 


let ppl:(string * string) list = [ 
    ("1", "Jerry"); 
    ("2", "Max"); 
    ("3", "Andrew"); 
] 

/// Generates a Person XML Element, given a tuple. 
let createPerson (id:string, name:string) = new XElement(XName.Get("Person"), 
               new XAttribute(XName.Get("ID"), id), 
               new XElement(XName.Get("Name"), name) 
) 

/// Filter People by having odd ID's 
let oddFilter = fun (id:string, name:string) -> (System.Int32.Parse(id) % 2).Equals(1) 

/// Open filter which will return all people 
let allFilter = fun (id:string, name:string) -> true 

/// Generates a People XML Element. 
let createPeople filter = new XElement(XName.Get("People"), 
           ppl |> List.filter(filter) |> List.map createPerson 
) 

/// First XML Object 
let XmlA = createPeople oddFilter 

/// Second XML Object 
let XmlB = createPeople allFilter 


printf "%A\n\n%A" XmlA XmlB 


/// Waits for a keypress 
let pauseKey = fun() -> System.Console.ReadKey() |> ignore 


pauseKey() 

Le mie domande sono: Quali sono le cose ho fatto bene in questo scenario? Quali parti potrebbero essere fatte meglio?

Non vedo davvero l'ora di alcune idee e sono abbastanza entusiasta di conoscere anche i paradigmi funzionali! :)

Grazie in anticipo

+0

+1 Nella stessa identica situazione qui (ma con codice diverso ovviamente) è bello vedere la risposta a questo. – Jay

+1

F # è fatto fondamentalmente in modo che rappresenti il ​​tipo per te, ha un motore di inferenza molto forte, quindi dovresti abituarti a omettere l'annotazione del tipo e renderti le funzioni più generiche che puoi – 0xFF

+0

@martain_net, grazie per il suggerimento, che rende molto senso in realtà :) Penso di aver abusato della definizione del tipo per capire i diversi tipi nativi di F # (es. lista (stringa * stringa) significa lista di tuple con 2 stringhe). – Russell

risposta

13

In linea di principio, il codice è tutto a posto.

Ci sono solo alcuni punti che possono essere semplificati da un punto di vista sintattico.

let ppl:(string * string) list = [ 
    ("1", "Jerry"); 
    ("2", "Max"); 
    ("3", "Andrew"); 
] 

Il compilatore è in grado di dedurre maggior parte dei tipi da lui stesso:

let ppl = [ "1", "Jerry"; 
      "2", "Max"; 
      "3", "Andrew" ] 

E naturalmente è possibile ri-scrivere i filtri come questo a causa di currying:

let oddFilter (id:string, name:string) = (int id) % 2 = 1 
let allFilter (id:string, name:string) = true 

Il più grande il miglioramento sarebbe la separazione degli indici dai nomi e lasciare che il programma esegua la numerazione.Non è necessario lavorare con le stringhe al posto dei numeri e può utilizzare più idiomatiche funzioni senza parametri:

let ppl = [ "Jerry"; "Max"; "Andrew" ] 

let oddFilter id name = id % 2 = 1 
let allFilter id name = true 

let createPerson id name = ... 

La parte

ppl |> List.filter(filter) |> List.map createPerson 

sarebbe riscritto per

[ for (index, name) in List.mapi (fun i x -> (i, x)) do 
     if filter index name then 
      yield createPerson (string index) name ] 
+2

Sì, digita inferenza e curry. Roba buona – cfern

+0

Grazie Dario, ci sono alcune buone idee. Ti spiace se ti chiedo perché riscrivere il ppl |> List.filter (filtro) |> List.map createPerson? Sono un po 'confuso dall'elaborazione. Sembra che ci siano alcuni argomenti che esaminerò.:) Thx again – Russell

+0

Il punto è solo quello di trattare gli indici in cui sono davvero necessari. Così li ho generati * dentro * 'createPeople'. – Dario

4
let createPeople filter = new XElement(XName.Get("People"), 
          ppl |> List.filter(filter) |> List.map createPerson 
) 

Questa parte può essere deforested manualmente, oppure è possibile sperare che il compilatore deforest per voi.

Fondamentalmente, c'è una struttura intermedia (la lista delle persone filtrate) che, se questa è compilata in modo ingenuo, sarà assegnata a servire una sola volta. Meglio applicare createPerson su ciascun elemento, in quanto è deciso se sono dentro o fuori e creare direttamente il risultato finale.

EDIT: cfern contribuito questa versione disboscata di createPeople:

let createPeople filter = 
    new XElement(
    XName.Get("People"), 
    List.foldBack 
     (fun P acc -> if filter P then (createPerson P)::acc else acc) 
     ppl 
     []) 

NOTA: perché ci potrebbero essere effetti collaterali a filter o createPerson, in F # è piuttosto difficile per il compilatore di decidere a disboscare da si. In questo caso mi sembra che la deforestazione sia corretta perché anche se lo filter ha effetti collaterali, non lo è lo createPerson, ma non sono uno specialista.

+2

Non posso testarlo subito, ma potresti sostituire List.filter e List.map con Seq.filter e Seq.map per evitare di creare liste intermedie? – cfern

+0

@cfern Se continui a fare un 'filtro' seguito da una' mappa', c'è ancora una struttura intermedia che viene allocata, usata una volta e poi scartata. Stavo pensando più a lungo di rimpiazzarli entrambi con un singolo 'fold' che applica' createPerson' e aggiunge il risultato all'accumulatore solo se 'filter' è positivo. –

+1

Ah, capisco. Sono ancora nuovo alla programmazione funzionale e quindi non ancora completamente utilizzato per le pieghe. E 'questo quello a cui stai pensando? ... lascia creare createPeople filter = new XElement (XName.Get ("People"), List.foldBack (fun P acc -> se filter P then (createPerson P) :: acc else acc) ppl []) ... dove Ho usato foldBack per preservare l'ordine delle persone. – cfern

3

Most del tempo che deforestazione senza una ragione specifica, generalmente prestazioni, è una cattiva idea. Quale di questi ritieni più facile da leggere e meno incline agli errori? La deforestazione presa fuori dal contesto aggiunge solo complessità e/o accoppiamento al codice.

let createPeople filter ppl = 
    ppl 
    |> List.mapi (fun i x -> (i, x)) 
    |> List.filter filter 
    |> List.map createPerson 

let createPeople filter ppl = 
    [ for (index, name) in ppl |> List.mapi (fun i x -> (i, x)) do 
      if filter (index, name) then 
       yield createPerson (index, string) ] 

let createPeople filter ppl = 
    (ppl |> List.mapi (fun i x -> (i, x)), []) 
    ||> List.foldBack (fun P acc -> if filter P then (createPerson P)::acc else acc) 

volta ci si abitua alla composizione funzione di sintassi consente di escludere ppl.

let createPeople filter = 
    List.mapi (fun i x -> (i, x)) 
    >> List.filter filter 
    >> List.map createPerson 

Tutti utilizzano dati tupla.

let filter (id, name) = 
    id % 2 = 1 

let allFilter (id, name) = 
    true 

let createPerson (id, name) = 
    () 
+0

grazie a gradbot. Per curiosità, cosa significa >> in F #? È un operatore di tubazioni come |>? O è un operatore bit a bit (spostando i bit a destra)? – Russell

+0

Unisce due funzioni insieme alla funzione a sinistra che viene chiamata per prima. È definito come let (>>) f g x = g (f x) – TimothyP

0

Ho anche recentemente avuto bisogno di trasformare un XSL in un file XML. Questa è la F # che ho usato per fare questo.

Ha alcune peculiarità interessanti nell'usare i metodi .net.

(* Transforms an XML document given an XSLT. *) 

open System.IO 
open System.Text 
open System.Xml 
open System.Xml.Xsl 

let path = @"C:\\XSL\\" 

let file_xml = path + "test.xml" 
let file_xsl = path + "xml-to-xhtml.xsl" 

(* Compile XSL file to allow transforms *) 
let compile_xsl (xsl_file:string) = new XslCompiledTransform() |> (fun compiled -> compiled.Load(xsl_file); compiled) 
let load_xml (xml_file:string) = new XmlDocument() |> (fun doc -> doc.Load(xml_file); doc) 

(* Transform an Xml document given an XSL (compiled *) 
let transform (xsl_file:string) (xml_file:string) = 
     new MemoryStream() 
     |> (fun mem -> (compile_xsl xsl_file).Transform((load_xml xml_file), new XmlTextWriter(mem, Encoding.UTF8)); mem) 
     |> (fun mem -> mem.Position <- (int64)0; mem.ToArray()) 

(* Return an Xml fo document that has been transformed *) 
transform file_xsl file_xml 
    |> (fun bytes -> File.WriteAllBytes(path + "out.html", bytes))