[英]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: 我没有时间把它写成代码,但是:
seq.map_concat
to go from the array of properties returned by GetProperties()
to a sequence of (property, attribute) tuples. seq.map_concat
从GetProperties()
返回的属性数组转到(属性,属性)元组序列。 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.