f # قابلة للتغيير إلى غير قابل للتغيير
-
23-08-2019 - |
سؤال
gday كل شيء،
لقد كنت حذرا في بعض F # من وقت متأخر وتوصلت إلى منشئ السلسلة التالية التي قمت باستمرارها من رمز C #. يقوم بتحويل كائن إلى سلسلة شريطة أن يمر Regex المحدد في السمات. من المحتمل أن تكون مبالغة للمهمة في متناول اليد ولكن لأغراض التعلم.
يستخدم عضو BuilString حاليا وتحديثا سلسلة متغيرة للتغيير. لقد كنت أرفف ذهني للعمل بطريقة تفعل هذا دون أي أشياء قابلة للتغيير دون جدوى. الذي يجلب لي إلى سؤالي.
هل من الممكن تنفيذ وظيفة عضو BuilString دون أي كائنات قابلة للتغيير؟
هتافات،
ميخائيل
//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
المحلول
أعتقد أنه يمكنك دائما تحويل "للحلقة على تسلسل مع تأثير واحد فقط (تخفيض متغير محلي)" في التعليمات البرمجية التي تتخلص من القابلة للتغيير؛ إليك مثال على التحويل العام:
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" طوال الوقت.)
قد يكون من المفيد محاولة القيام بهذا التحويل نفسه على رمز المثال الخاص بك ونشر إجابتك الخاصة. لن يؤدي هذا بالضرورة إلى أعلى رمز الاصطلاح، ولكن يمكن أن يكون تمرينا في تحويل حلقة إلى مكالمة SEQ.POLD.
(يقال، في هذا المثال، الهدف بأكمله هو في الغالب تمرينا أكاديميا - الرمز مع قابلة للتغيير "جيد".)
نصائح أخرى
ليس لدي الوقت لكتابة هذا الأمر كود، ولكن:
- اكتب دالة تمثل الجزء الأعمق، مع أخذ سلسلة (النتيجة حتى الآن) و Tuple الخاص بالخاصية والسمة، وإرجاع السلسلة بعد الاستبدال.
- يستخدم
seq.map_concat
للذهاب من مجموعة من العقارات التي تعادGetProperties()
إلى تسلسل (الممتلكات، السمة) tuples. - يستخدم
seq.fold
مع الشريطين السابقين للقيام بالتحول بأكمله، باستخدام القالب الأصلي كقيمة أولية للتجميع. ستكون النتيجة الإجمالية هي السلسلة النهائية المستبدلة.
هل هذا منطقي؟
نهج وظيفي بحت:
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