【问题标题】:F# Mutable to ImmutableF# 可变到不可变
【发布时间】:2009-07-22 01:47:30
【问题描述】:

所有节日,

我最近一直在涉足一些 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

【问题讨论】:

    标签: reflection f# immutability mutable


    【解决方案1】:

    我认为您始终可以将“仅具有一种效果(改变局部变量)的序列上的 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 调用的练习。

    (也就是说,在这个例子中,整个目标主要是一个学术练习 - 带有可变变量的代码是“好的”。)

    【讨论】:

    • 干杯布赖恩和乔恩。我将实现折叠,看看结果如何。就像我在问题中提到的那样,这纯粹是一项学术活动。
    【解决方案2】:

    我没有时间把它写成代码,但是:

    • 编写一个表示最里面部分的函数,获取一个字符串(到目前为止的结果)和一个属性和属性的元组,并返回替换后的字符串。
    • 使用seq.map_concatGetProperties() 返回的属性数组转到(属性、属性)元组序列。
    • 使用seq.fold 和前两位进行整个转换,使用原始模板作为聚合的初始值。总体结果将是最终替换的字符串。

    这有意义吗?

    【讨论】:

      【解决方案3】:

      纯粹的函数式方法:

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-06-24
        • 1970-01-01
        • 1970-01-01
        • 2014-08-18
        • 1970-01-01
        • 2013-04-17
        • 1970-01-01
        相关资源
        最近更新 更多