برای بهروزرسانی رکوردهای تودرتو از lensها استفاده کنید (مخصوص زبانهایی که دادههای تغییرناپذیر دارند).
بهروزرسانی فیلدهای رکوردهای تودرتوی تغییرناپذیر تا حدی آزاردهنده است.
کد چنین مواردی به همان اندازه که ساختار عمیق است، دستوپاگیر میشود.
فرض کنید یک Person دارید که شامل یک Address است، آن هم یک Street دارد که یک Number دارد؛ بهروزرسانی Number نیازمند ساختن یک Street تازه با Number تازه است، بعد یک Address تازه با Street تازه و در نهایت یک Person تازه با Address تازه.
گیج شدهاید؟
یکی از راهحلهای این مسئله استفاده از lenses است.
چند تابع برای دسترسی به رکوردها را با استفاده از lensها پیادهسازی کنید. مجموعهی testها هم به شما این امکان را میدهد که اصلاً از lensها استفاده نکنید و با روشهای متفاوت آزمایش کنید.
«لنز»ها در Roc زیاد استفاده نمیشوند، چون نحوهی نگارش بهروزرسانی رکورد بیشتر موارد استفاده را بهخوبی پوشش میدهد. برای مثال، این رکورد تودرتو را در نظر بگیرید:
bob = { hair: { color: Brown, length: Long }, mouth: Smiling, height: 6.0 }
حالا بیایید موی باب را کوتاه کنیم:
new_bob = { ..bob, hair: { ..bob.hair, length: Short } }
هیچ لنزی لازم نیست! با این حال، هرچند لنزها در Roc کماند، در زبانهای دیگری مانند Haskell و Scala رایجاند. شاید کنجکاو باشید بدانید چطور میتوان آنها را در Roc پیادهسازی کرد. این روش خوبی هم هست برای تمرین برخی قابلیتهای پیشرفتهی Roc مثل انواع پارامتری و رکوردهایی که شامل توابع هستند. در واقع، این یکی از راههای نمایش یک لنز در Roc است:
Lens(whole, part) := {
get : whole -> part,
set : whole, part -> whole,
}
این یک نوع پارامتری است: whole و part میتوانند هر نوعی باشند. whole معمولاً یک رکورد است، در حالی که part میتواند هر نوع فیلدی باشد. فیلد get شامل تابعی است که یک part مشخص از whole دادهشده را برمیگرداند و فیلد set شامل تابعی است که نسخهای از whole دادهشده را برمیگرداند، با این تفاوت که یک part مشخص با part دادهشده جایگزین شده است.
برای مثال، این لنزی است برای گرفتن یا تنظیم موی یک شخص:
hair_lens = {
get: |person| person.hair,
set: |person, hair| { ..person, hair },
}
میتوانیم از این لنز استفاده کنیم تا روی موی باب تمرکز کنیم، به این شکل:
bob_hair = bob |> hair_lens.get
و میتوانیم مویش را به این شکل تغییر دهیم:
new_hair = { ..bob.hair, length: Short }
new_bob = bob |> (hair_lens.set)(new_hair)
توجه: پرانتزهای دور hair_lens.set الزامیاند: بدون آنها، Roc به دنبال تابعی به اسم set در نوع hair_lens میگردد.
لنزها وقتی بهویژه مفید میشوند که شروع به ترکیبشان کنید؛ آنوقت میتوانید بخشهای عمیقاً تودرتو را بگیرید و تنظیم کنید. این کار با پیادهسازی تابعی مانند این انجام میشود:
compose : Lens(outer, inner), Lens(inner, part) -> Lens(outer, part)
وقتی این تابع پیادهسازی شد، میتوانید از آن برای ساختن لنزی برای طول مو به این شکل استفاده کنید:
length_lens = {
get: |hair| hair.length,
set: |hair, length| { ..hair, length },
}
hair_length_lens = compose(hair_lens, length_lens)
حالا میتوانید موی باب را دوباره کوتاه کنید:
set_hair_length = hair_length_lens.set
new_bob = bob |> set_hair_length(Short)
همچنین ممکن است بخواهید تابعی پیادهسازی کنید که به شما اجازه میدهد هر تبدیلی را روی یک بخش تودرتو اعمال کنید:
over : Lens(whole, part), whole, (part -> part) -> whole
وقتی پیادهسازی شد، میتوانید از over به این شکل استفاده کنید:
cut_hair = |current_length| match current_length {
Long => Short
Short => Bald
Bald => Bald
}
new_bob = hair_length_lens |> over(bob, cut_hair)
در این تمرین، تستها بررسی میکنند که کد شما بتواند بخشهای تودرتوی مختلفی از رکورد تودرتوی زیر را تغییر دهد:
test_person : Person
test_person = {
name: { forenames: "Jane Joanna", surname: "Doe" },
birth: {
born_at: { street: "Longway", house_number: 1024, place: "Springfield", country: "United States" },
born_on: { year: 1984, month: 4, day: 12 },
},
address: { street: "Shortlane", house_number: 2, place: "Fallmeadow", country: "Canada" },
}
توصیه میکنیم اول تستها را با استفاده از نحوهی نگارش بهروزرسانی رکورد در Roc پاس کنید، بعد بهجای آن استفاده از لنزها را امتحان کنید.