F # изменчивый в неизменный - PullRequest
4 голосов
/ 21 мая 2009

Gday All,

В последнее время я немного увлекаюсь F # и придумала следующий построитель строк, портированный из некоторого кода C #. Он преобразует объект в строку при условии, что ему передано регулярное выражение, определенное в атрибутах. Вероятно, это излишне для выполняемой задачи, но для целей обучения.

В настоящее время член BuildString использует изменяемую строковую переменную updatedTemplate. Я ломал голову, пытаясь найти способ сделать это без каких-либо изменяемых объектов, но безрезультатно. Что подводит меня к моему вопросу.

Возможно ли реализовать функцию-член BuildString без каких-либо изменяемых объектов?

Приветствия

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

Ответы [ 3 ]

1 голос
/ 21 мая 2009

Я думаю, что вы всегда можете преобразовать цикл 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.

(Тем не менее, в этом примере вся цель - в основном академическое упражнение - код с изменяемым 'отлично'.)

1 голос
/ 21 июля 2009

Чисто функциональный подход:

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
1 голос
/ 21 мая 2009

У меня нет времени, чтобы записать это как код, но:

  • Напишите функцию, представляющую самую внутреннюю часть, взяв строку (пока результат) и кортеж свойства и атрибута и вернув строку после замены.
  • Используйте seq.map_concat, чтобы перейти от массива свойств, возвращаемых GetProperties(), к последовательности кортежей (свойство, атрибут).
  • Используйте seq.fold с двумя предыдущими битами, чтобы выполнить полное преобразование, используя исходный шаблон в качестве начального значения для агрегации. Общий результат будет окончательной замененной строкой.

Имеет ли это смысл?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...