F# 可变到不可变

发布于 2024-07-20 02:33:45 字数 1704 浏览 4 评论 0原文

天哪,

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

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. 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. 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?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

↘人皮目录ツ 2024-07-27 02:33:45

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

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

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; 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.)

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.

(That said, in this example the whole goal is mostly an academic exercise - the code with the mutable is 'fine'.)

蓝戈者 2024-07-27 02:33:45

我没有时间将其写成代码,但是:

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

那有意义吗?

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.
  • Use seq.fold with the previous two bits to do the whole transformation, using the original template as the initial value for the aggregation. The overall result will be the final replaced string.

Does that make sense?

因为看清所以看轻 2024-07-27 02:33:45

纯粹的函数式方法:

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文