简体   繁体   English

F#可变为不可变

[英]F# Mutable to Immutable

Gday All, Gday All,

I have been dabbling in some F# of late and I came up with the following string builder that I ported from some C# code. 我一直在涉及一些F#,我想出了一些我从一些C#代码中移植的字符串构建器。 It converts an object into a string provided it passes a Regex defined in the attributes. 它将对象转换为字符串,前提是它传递了属性中定义的正则表达式。 Its probably overkill for the task at hand but its for learning purposes. 它可能对手头的任务有些过分,但出于学习目的。

Currently the BuildString member uses a mutable string variable updatedTemplate. 目前,BuildString成员使用可变字符串变量updatedTemplate。 I have been racking my brain to work out a way doing this without any mutable objects to no avail. 我一直绞尽脑汁去研究这样做,没有任何可变对象也无济于事。 Which brings me to my question. 这让我想到了我的问题。

Is it possible to implement the BuildString member function without any mutable objects? 是否可以在没有任何可变对象的情况下实现BuildString成员函数?

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

A purely functional approach: 一种纯功能方法:

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

I don't have the time to write this up as code, but: 我没有时间把它写成代码,但是:

  • Write a function representing the innermost part, taking a string (the result so far) and a tuple of the property and the attribute, and returning the string after the replacement. 编写一个表示最里面部分的函数,取一个字符串(到目前为止的结果)和属性和属性的元组,并在替换后返回字符串。
  • Use seq.map_concat to go from the array of properties returned by GetProperties() to a sequence of (property, attribute) tuples. 使用seq.map_concatGetProperties()返回的属性数组转到(属性,属性)元组序列。
  • Use seq.fold with the previous two bits to do the whole transformation, using the original template as the initial value for the aggregation. 使用seq.fold和前两位进行整个转换,使用原始模板作为聚合的初始值。 The overall result will be the final replaced string. 总体结果将是最终替换的字符串。

Does that make sense? 那有意义吗?

I think you can always transform a "for loop over a sequence with only one effect (mutating a local variable)" into code that gets rid of the mutable; 我认为你总是可以将“只有一个效果的序列for for循环(变异局部变量)”变换为去掉变量的代码; here's an example of the general transform: 这是一般变换的一个例子:

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

Your particular example has nested loops, so here's an example of showing the same kind of mechanical transform in two steps: 您的特定示例具有嵌套循环,因此这是一个通过两个步骤显示相同类型的机械变换的示例:

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

(In the code above, I used the names 'x2' and 'x3' to make scoping more apparent, but you can just use the name 'x' throughout.) (在上面的代码中,我使用名称'x2'和'x3'来使范围更明显,但你可以在整个过程中使用名称'x'。)

It may be worthwhile to try to do this same transform on your example code and post your own answer. 尝试对示例代码执行相同的转换并发布您自己的答案可能是值得的。 This won't necessarily yield the most idiomatic code, but can be an exercise in transforming a for loop into a Seq.fold call. 这不一定会产生最惯用的代码,但可以是将for循环转换为Seq.fold调用的练习。

(That said, in this example the whole goal is mostly an academic exercise - the code with the mutable is 'fine'.) (也就是说,在这个例子中,整个目标主要是学术练习 - 具有可变性的代码是'很好'。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM