F # Изменяемый на Неизменяемый
-
23-08-2019 - |
Вопрос
Добрый День Всем,
В последнее время я немного поиграл с F #, и я придумал следующий конструктор строк, который я портировал из некоторого кода C #.Он преобразует объект в строку при условии, что он передает регулярное выражение, определенное в атрибутах.Это, вероятно, перебор для поставленной задачи, но это для целей обучения.
В настоящее время элемент BuildString использует изменяемую строковую переменную updatedTemplate.Я ломал голову, чтобы придумать способ сделать это без каких-либо изменяемых объектов, но безрезультатно.Что подводит меня к моему вопросу.
Возможно ли реализовать функцию-член BuildString без каких-либо изменяемых объектов?
Ваше здоровье,
Майкл
//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
Решение
Я думаю, вы всегда можете преобразовать "цикл for по последовательности только с одним эффектом (изменение локальной переменной)" в код, который избавляется от изменяемого;вот пример общего преобразования:
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
В вашем конкретном примере есть вложенные циклы, поэтому вот пример демонстрации такого же механического преобразования в два этапа:
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
(В приведенном выше коде я использовал имена 'x2' и 'x3', чтобы сделать область видимости более наглядной, но вы можете просто использовать имя 'x' повсюду.)
Возможно, стоит попробовать выполнить это же преобразование в вашем примере кода и опубликовать свой собственный ответ.Это не обязательно приведет к наиболее идиоматичному коду, но может быть упражнением в преобразовании цикла for в вызов Seq.fold.
(Тем не менее, в этом примере вся цель в основном является академическим упражнением - код с изменяемымзначением "в порядке".)
Другие советы
У меня нет времени записывать это в виде кода, но:
- Напишите функцию, представляющую самую внутреннюю часть, берущую строку (пока результат) и кортеж свойства и атрибута и возвращающую строку после замены.
- Использование
seq.map_concat
чтобы перейти из массива свойств, возвращаемыхGetProperties()
к последовательности кортежей (свойство, атрибут). - Использование
seq.fold
с помощью предыдущих двух битов выполнить полное преобразование, используя исходный шаблон в качестве начального значения для агрегирования.Общим результатом будет окончательная замененная строка.
Есть ли в этом смысл?
Чисто функциональный подход:
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