F # Mutevole a Immutabile
-
23-08-2019 - |
Domanda
Tutti Gday,
Sono stato dilettarsi in qualche F # di ritardo e mi si avvicinò con la seguente stringa builder che ho portato da qualche codice C #. Si converte un oggetto in una stringa condizione che passa un Regex definito negli attributi. La sua probabilmente eccessivo per il compito a portata di mano, ma la sua a fini di apprendimento.
Al momento il membro BuildString utilizza una stringa mutabile updatedTemplate variabile. Ho tormentando il mio cervello per trovare un modo di fare questo senza oggetti mutabili senza alcun risultato. Il che mi porta alla mia domanda.
E 'possibile implementare la funzione di membro BuildString senza oggetti mutabili?
Saluti,
Michael
//The Validation Attribute
type public InputRegexAttribute public (format : string) as this =
inherit Attribute()
member self.Format with get() = format
//The class definition
type public Foo public (firstName, familyName) as this =
[<InputRegex("^[a-zA-Z\s]+$")>]
member self.FirstName with get() = firstName
[<InputRegex("^[a-zA-Z\s]+$")>]
member self.FamilyName with get() = familyName
module ObjectExtensions =
type System.Object with
member this.BuildString template =
let mutable updatedTemplate : string = template
for prop in this.GetType().GetProperties() do
for attribute in prop.GetCustomAttributes(typeof<InputRegexAttribute>,true).Cast<InputRegexAttribute>() do
let regex = new Regex(attribute.Format)
let value = prop.GetValue(this, null).ToString()
if regex.IsMatch(value) then
updatedTemplate <- updatedTemplate.Replace("{" + prop.Name + "}", value)
else
raise (new Exception "Regex Failed")
updatedTemplate
open ObjectExtensions
try
let foo = new Foo("Jane", "Doe")
let out = foo.BuildInputString("Hello {FirstName} {FamilyName}! How Are you?")
printf "%s" out
with | e -> printf "%s" e.Message
Soluzione
Penso che si può sempre trasformare un "ciclo for su una sequenza con un solo effetto (mutando una variabile locale)" in codice che si sbarazza del mutabile; ecco un esempio del generale di trasformare:
let inputSeq = [1;2;3]
// original mutable
let mutable x = ""
for n in inputSeq do
let nStr = n.ToString()
x <- x + nStr
printfn "result: '%s'" x
// immutable
let result =
inputSeq |> Seq.fold (fun x n ->
// the 'loop' body, which returns
// a new value rather than updating a mutable
let nStr = n.ToString()
x + nStr
) "" // the initial value
printfn "result: '%s'" result
Il tuo esempio particolare è annidato loop, ecco un esempio di mostrare lo stesso tipo di meccanica trasformare in due fasi:
let inputSeq1 = [1;2;3]
let inputSeq2 = ["A";"B"]
let Original() =
let mutable x = ""
for n in inputSeq1 do
for s in inputSeq2 do
let nStr = n.ToString()
x <- x + nStr + s
printfn "result: '%s'" x
let FirstTransformInnerLoopToFold() =
let mutable x = ""
for n in inputSeq1 do
x <- inputSeq2 |> Seq.fold (fun x2 s ->
let nStr = n.ToString()
x2 + nStr + s
) x
printfn "result: '%s'" x
let NextTransformOuterLoopToFold() =
let result =
inputSeq1 |> Seq.fold (fun x3 n ->
inputSeq2 |> Seq.fold (fun x2 s ->
let nStr = n.ToString()
x2 + nStr + s
) x3
) ""
printfn "result: '%s'" result
(Nel codice di cui sopra, ho usato i nomi 'x2' e 'x3' per rendere scoping più evidente, ma si può semplicemente utilizzare il nome di 'X' in tutto.)
Può essere utile per cercare di fare questa stessa trasformazione su vostro codice di esempio e comunica la tua propria risposta. Questo non sarà necessariamente cedere il codice più idiomatica, ma può essere un esercizio di trasformare un ciclo in una chiamata Seq.fold.
(Detto questo, in questo esempio, l'intero obiettivo è per lo più un esercizio accademico -. Il codice con il mutevole è 'bene')
Altri suggerimenti
Non ho il tempo per scrivere questo come codice, ma:
- scriva una funzione che rappresenta la parte più interna, prendendo una stringa (il risultato finora) e una tupla della proprietà e l'attributo, e restituendo la stringa dopo la sostituzione.
- Usa
seq.map_concat
per passare dalla matrice di proprietà restituite daGetProperties()
ad una sequenza di (immobili, attributo) tuple. - Usa
seq.fold
con i precedenti due bit per fare l'intero trasformazione, utilizzando il modello originale come valore iniziale per l'aggregazione. Il risultato complessivo sarà la stringa di sostituzione finale.
Ha senso?
Un approccio puramente funzionale:
module ObjectExtensions =
type System.Object with
member this.BuildString template =
let properties = Array.to_list (this.GetType().GetProperties())
let rec updateFromProperties (pps : Reflection.PropertyInfo list) template =
if pps = List.Empty then
template
else
let property = List.hd pps
let attributes = Array.to_list (property.GetCustomAttributes(typeof<InputRegexAttribute>,true))
let rec updateFromAttributes (ats : obj list) (prop : Reflection.PropertyInfo) (template : string) =
if ats = List.Empty then
template
else
let a = (List.hd ats) :?> InputRegexAttribute
let regex = new Regex(a.Format)
let value = prop.GetValue(this, null).ToString()
if regex.IsMatch(value) then
template.Replace("{" + prop.Name + "}", value)
else
raise (new Exception "Regex Failed\n")
updateFromProperties(List.tl pps) (updateFromAttributes attributes property template)
updateFromProperties properties template