F # Mutable à Immuable
-
23-08-2019 - |
Question
Gday Tous,
Je suis barboter dans une F # de la fin et je suis venu avec le constructeur de chaîne suivante que je Ported à partir du code C #. Il convertit un objet dans une chaîne de caractères à condition qu'il passe une expression régulière définie dans les attributs. Son probablement trop pour la tâche à accomplir, mais son à des fins d'apprentissage.
Actuellement, le membre BuildString utilise une updatedTemplate variable de chaîne mutable. J'ai creusé la cervelle pour trouver un moyen de faire cela sans aucun objet mutables en vain. Ce qui me amène à ma question.
Est-il possible de mettre en œuvre la fonction de membre BuildString sans objets mutables?
Cheers,
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
La solution
Je pense que vous pouvez toujours transformer une « boucle sur une séquence avec un seul effet (une variable locale muter) » dans le code qui se débarrasse de la mutable; voici un exemple de la transformation générale:
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
Votre exemple particulier a des boucles imbriquées, voici donc un exemple de montrer le même genre de mécanique transformer en deux étapes:
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
(Dans le code ci-dessus, je les noms de x2 'et « x 3 » pour rendre la portée plus apparente, mais vous pouvez simplement utiliser le nom « x » tout au long.)
Il peut être intéressant d'essayer de faire ce même transform sur votre code d'exemple et publiez votre propre réponse. Cela donnera pas nécessairement le code le plus idiomatiques, mais peut être un exercice à transformer une boucle en un appel Seq.fold.
(Cela dit, dans cet exemple, l'ensemble but est essentiellement un exercice académique -. Le code avec le mutable est « bien »)
Autres conseils
Je n'ai pas le temps d'écrire ceci comme code, mais:
- Ecrire une fonction représentant la partie la plus profonde, en une chaîne (le résultat jusqu'à présent) et un tuple de la propriété et l'attribut, et retourner la chaîne après le remplacement.
- Utiliser
seq.map_concat
pour aller de l'éventail de propriétés renvoyées parGetProperties()
à une séquence de (propriété, attribut) tuples. - Utilisation
seq.fold
avec les deux bits précédents pour faire toute la transformation, en utilisant le modèle d'origine en tant que valeur initiale pour l'agrégation. Le résultat global sera la chaîne remplacée finale.
Est-ce logique?
Une approche purement fonctionnelle:
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