מסמך זה מיועד למפתח Scala3 המתחיל שכבר בקי בפרוזה של Scala, אך מתלבט לגבי כל ה`implicits
` ותכונות פרמטריות בקוד.
מסמך זה מסביר את הסיבה, איך, היכן ומתי סוגים (TC).
לאחר קריאת מסמך זה, מפתח Scala3 המתחיל ירכוש ידע מוצק לשימוש ויצלול לתוך קוד המקור שלו הרבה של ספריות Scala ולהתחיל לכתוב קוד Scala אידיומטי.
בואו נתחיל עם הסיבה…
בעיית הביטוי
ב1998, אמר פיליפ ואדלר ש"בעיית הביטוי היא שם חדש לבעיה ישנה". זו הבעיה של הרחבה של תוכנה. על פי כתב מר ודלר, הפתרון לבעיית הביטוי חייב לעמוד בכללים הבאים:
- כלל 1: אפשר את היישום של התנהגויות קיימות (חשבו על תכונת Scala) שיש ליישם עליה ייצוגים חדשים (חשבו על מחלקת מקרה)
- כלל 2: אפשר את היישום של התנהגויות חדשות להחיל עליו ייצוגים קיימים
- כלל 3: אסור לו לסכן את בטיחות סוג
- כלל 4: אין צורך לבצע קומפילציה מחדש קוד קיים
פתרון בעיה זו יהיה חוט הכסף של מאמר זה.
כלל 1: יישום התנהגות קיימת על ייצוג חדש
לכל שפה מונחה עצמים יש פתרון אפוי עבור כלל 1 עם תת -סוג פולימורפיזם. אתה יכול ליישם בבטחה כל `trait
` מוגדר בתלות ב`class
` בקוד משלך, מבלי להדר מחדש את התלות. בוא נראה את זה בפעולה:
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
val eth = Lib1.Ethereum()
val btc = Lib1.Bitcoin()
val dot = Lib2.Polkadot()
בדוגמה הפיקטיבית הזו, ספרייה `Lib1
` (שורה 5) מגדירה תכונה `Blockchain
` (שורה 6) עם 2 יישומים שלה (שורות 9 ו-12). `Lib1
` יישאר זהה בכל המסמך הזה (אכיפה של כלל 4).
`Lib2
` (שורה 15) מיישם את ההתנהגות הקיימת `Blockchain
`בכיתה חדשה`Polkadot
` (כלל 1) בצורה בטוחה (כלל 3), ללא הידור מחדש `Lib1
` (כלל 4).
כלל 2: יישום של התנהגויות חדשות שיושמו על ייצוגים קיימים
בואו נדמיין ב`Lib2
`אנחנו רוצים התנהגות חדשה`lastBlock
` ליישום ספציפי עבור כל אחד `Blockchain
`.
הדבר הראשון שעולה על הדעת הוא יצירת מתג גדול המבוסס על סוג הפרמטר.
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(blockchain: Blockchain): Block = blockchain match
case _:Ethereum => todo
case _:Bitcoin => todo
case _:Polkadot => todo
object Lib3:
import Lib1.*
case class Polygon() extends Blockchain:
override def getBlock(height: Height): Block = todo
import Lib1.*, Lib2.*, Lib3.*
println(lastBlock(Bitcoin()))
println(lastBlock(Ethereum()))
println(lastBlock(Polkadot()))
println(lastBlock(Polygon()))
פתרון זה הוא יישום מחדש חלש של פולימורפיזם מבוסס סוג שכבר אפוי בשפה!
`Lib1
` נותר ללא נגיעה (זכור, כלל 4 נאכף בכל מסמך זה).
הפתרון מיושם ב-`Lib2
`הוא בסדר עד שיוצג עוד בלוקצ'יין ב-`Lib3
`. זה מפר את כלל בטיחות הסוג (כלל 3) מכיוון שקוד זה נכשל בזמן ריצה בשורה 37. ומשנה `Lib2
` יפר את כלל 4.
פתרון נוסף הוא שימוש ב- `extension
`.
def todo = 42
type Height = Int
type Block = Int
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
object Lib2:
import Lib1.*
case class Polkadot() extends Blockchain:
override def getBlock(height: Height): Block = todo
def lastBlock(): Block = todo
extension (eth: Ethereum) def lastBlock(): Block = todo
extension (btc: Bitcoin) def lastBlock(): Block = todo
import Lib1.*, Lib2.*
println(Bitcoin().lastBlock())
println(Ethereum().lastBlock())
println(Polkadot().lastBlock())
def polymorphic(blockchain: Blockchain) =
// blockchain.lastBlock()
???
`Lib1
` נותר ללא נגיעה (אכיפה של כלל 4 בכל המסמך).
`Lib2
` מגדיר התנהגות עבור הסוג שלו (שורה 21) ו-`הרחבה` עבור סוגים קיימים (שורות 23 ו-25).
שורות 28-30, ניתן להשתמש בהתנהגות החדשה בכל מחלקה.
אבל אין דרך לקרוא להתנהגות החדשה הזו בצורה פולימורפית (שורה 32). כל ניסיון לעשות זאת מוביל לשגיאות קומפילציה (שורה 33) או למתגים מבוססי הקלדה.
כלל מס' 2 זה מסובך. ניסינו ליישם את זה עם ההגדרה שלנו לפולימורפיזם וטריק 'הרחבה'. וזה היה מוזר.
יש קטע חסר בשם פולימורפיזם אד-הוק: היכולת לשלוח בבטחה יישום התנהגות לפי סוג, בכל מקום שבו ההתנהגות והסוג מוגדרים. להיכנס ל סוג Class דפוס.
דפוס ה-Type Class
למתכון הדגם Type Class (TC בקיצור) יש 3 שלבים.
- הגדירו התנהגות חדשה
- יישם את ההתנהגות
- השתמש בהתנהגות
בסעיף הבא, אני מיישם את דפוס ה-TC בצורה הפשוטה ביותר. זה מילולי, מסורבל ולא מעשי. אבל רגע, האזהרות האלה יתוקנו צעד אחר צעד בהמשך המסמך.
1. הגדירו התנהגות חדשה
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
`Lib1
` שוב נותר ללא פגע.
ההתנהגות החדשה is ה-TC התממש על ידי התכונה. הפונקציות המוגדרות בתכונה הן דרך ליישם כמה היבטים של התנהגות זו.
הפרמטר `A
` מייצג את הסוג שאנו רוצים להחיל עליו התנהגות, שהם תת-סוגים של `Blockchain
`במקרה שלנו.
כמה הערות:
- במידת הצורך, הסוג עם הפרמטר `
A
` ניתן להגביל עוד יותר על ידי המערכת מסוג Scala. למשל, נוכל לאכוף `A
`להיות `Blockchain
`. - כמו כן, ל-TC יכולות להיות הרבה יותר פונקציות המוצהרות בו.
- לבסוף, לכל פונקציה עשויים להיות הרבה יותר פרמטרים שרירותיים.
אבל בואו נשאיר דברים פשוטים למען הקריאה.
2. ליישם את ההתנהגות
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
עבור כל סוג החדש `LastBlock
` צפויה התנהגות, יש מופע ספציפי של התנהגות זו.
ה `Ethereum
`שורת יישום 22 מחושבת מתוך `eth
` מופע הועבר כפרמטר.
היישום של `LastBlock
`עבור `Bitcoin
`שורה 25 מיושמת עם IO לא מנוהל ואינה משתמשת בפרמטר שלו.
אז, `Lib2
`מיישמת התנהגות חדשה`LastBlock
`עבור `Lib1
`שיעורים.
3. השתמש בהתנהגות
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
val ethereumLastBlock = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
val bitcoinLastBlock = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A, behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2), ethereumLastBlock))
println(useLastBlock(Bitcoin(), bitcoinLastBlock))
שורה 30 `useLastBlock
` משתמש במופע של `A
` וה`LastBlock
` התנהגות שהוגדרה עבור המקרה הזה.
שורה 33 `useLastBlock
` נקרא עם מופע של `Ethereum
` ויישום של `LastBlock
` מוגדר ב`Lib2
`. שימו לב שאפשר לעבור כל יישום חלופי של `LastBlock[A]
` (תחשוב על הזרקת תלות).
`useLastBlock
` הוא הדבק בין הייצוג (ה-A בפועל) לבין ההתנהגות שלו. נתונים והתנהגות מופרדים, וזה מה שתכנות פונקציונלי דוגל בו.
דיון
בואו נסכם את הכללים של בעיית הביטוי:
- כלל 1: אפשר את היישום של התנהגויות קיימות להחיל עליו כיתות חדשות
- כלל 2: אפשר את היישום של התנהגויות חדשות להחיל עליו כיתות קיימות
- כלל 3: אסור לו לסכן את בטיחות סוג
- כלל 4: אין צורך לבצע קומפילציה מחדש קוד קיים
חוק 1 יכול להיפתר מהקופסה עם תת-סוג פולימורפיזם.
דפוס ה-TC שהוצג זה עתה (ראה צילום מסך קודם) פותר את כלל 2. זה בטוח מסוג (כלל 3) ומעולם לא נגענו `Lib1
` (כלל 4).
עם זאת, זה לא מעשי לשימוש מכמה סיבות:
- שורות 33-34 עלינו להעביר במפורש את ההתנהגות לאורך המופע שלה. זוהי תקורה נוספת. אנחנו צריכים פשוט לכתוב `
useLastBlock(Bitcoin())
`. - בשורה 31 התחביר אינו שכיח. נעדיף לכתוב תמציתית ומונחה יותר עצמים `
instance.lastBlock()
`הצהרה.
בואו נדגיש כמה תכונות של Scala לשימוש מעשי ב-TC.
חווית מפתח משופרת
ל- Scala יש סט ייחודי של תכונות וסוכרים תחביריים שהופכים את TC לחוויה מהנה באמת עבור מפתחים.
משתמע
ההיקף המרומז הוא היקף מיוחד שנפתר בזמן קומפילציה שבו רק מופע אחד מסוג נתון יכול להתקיים.
תוכנית מציבה מופע בטווח המרומז עם ה-`given
` מילת מפתח. לחלופין, תוכנית יכולה לאחזר מופע מהטווח המרומז עם מילת המפתח `using
`.
ההיקף המרומז נפתר בזמן ההידור, יש דרך ידועה לשנות אותו באופן דינמי בזמן ריצה. אם התוכנית מבצעת קומפילציה, ההיקף המרומז ייפתר. בזמן ריצה, לא ייתכן שחסרים מקרים מרומזים שבהם נעשה בהם שימוש. הבלבול האפשרי היחיד עשוי לנבוע משימוש במופע המרומז הלא נכון, אבל הבעיה הזו נותרה עבור היצור שבין הכיסא למקלדת.
זה שונה מהיקף גלובלי כי:
- זה נפתר בהקשר. שני מיקומים של תוכנית יכולים להשתמש במופע מאותו סוג נתון בהיקף מרומז, אך שני המופעים הללו עשויים להיות שונים.
- מאחורי הקלעים הקוד מעביר פונקציית ארגומנטים מרומזים לתפקד עד לשימוש המרומז. זה לא משתמש בשטח זיכרון גלובלי.
חוזרים לשיעור טיפוס! בואו ניקח את אותה דוגמה בדיוק.
def todo = 42
type Height = Int
type Block = Int
def http(uri: String): Block = todo
object Lib1:
trait Blockchain:
def getBlock(height: Height): Block
case class Ethereum() extends Blockchain:
override def getBlock(height: Height) = todo
case class Bitcoin() extends Blockchain:
override def getBlock(height: Height) = todo
`Lib1
` הוא אותו קוד ללא שינוי שהגדרנו בעבר.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given ethereumLastBlock:LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given bitcoinLastBlock:LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
import Lib1.*, Lib2.*
def useLastBlock[A](instance: A)(using behavior: LastBlock[A]) =
behavior.lastBlock(instance)
println(useLastBlock(Ethereum(lastBlock = 2)))
println(useLastBlock(Bitcoin()))
שורה 19 התנהגות חדשה `LastBlock
` מוגדר, בדיוק כמו שעשינו בעבר.
קו 22 וקו 25, `val
` מוחלף ב`given
`. שני ההטמעות של `LastBlock
` מוכנסים לטווח המרומז.
שורה 31 `useLastBlock
` מצהיר על ההתנהגות `LastBlock
` כפרמטר מרומז. המהדר פותר את המופע המתאים של `LastBlock
` מתוך היקף מרומז שהוגדר בהקשר ממיקומי מתקשרים (שורות 33 ו-34). קו 28 מייבא הכל מ`Lib2
`, כולל ההיקף המשתמע. אז, המהדר מעביר מופעים שהוגדרו שורות 22 ו-25 כפרמטר האחרון של `useLastBlock
`.
כמשתמש בספרייה, השימוש במחלקת סוג קל יותר מבעבר. בשורות 34 ו-35 מפתח צריך רק לוודא שמופע של ההתנהגות מוזרק בהיקף המרומז (וזה יכול להיות רק `import
`). אם משתמע אינו `given
` איפה הקוד `using
`זה, אומר לו המהדר.
הסקאלה המרומזת מקלה על המשימה של העברת מופעי כיתה יחד עם מופעים של התנהגויות שלהם.
סוכרים מרומזים
ניתן לשפר עוד יותר את שורות 22 ו-25 של הקוד הקודם! בואו נחזור על יישומי TC.
given LastBlock[Ethereum] = new LastBlock[Ethereum]:
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] = new LastBlock[Bitcoin]:
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
שורות 22 ו-25, אם שם המופע אינו בשימוש, ניתן להשמיט אותו.
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
שורות 22 ו-25, ניתן להחליף את החזרה על הסוג ב`with
` מילת מפתח.
given LastBlock[Ethereum] = _.lastBlock
given LastBlock[Bitcoin] = _ => http("http://bitcoin/last")
מכיוון שאנו משתמשים בתכונה מנוונת עם פונקציה בודדת בתוכה, ה-IDE עשוי להציע לפשט את הקוד באמצעות ביטוי SAM. למרות שזה נכון, אני לא חושב שזה שימוש נכון ב-SAM, אלא אם כן אתה משחק גולף כלאחר יד.
Scala מציעה סוכרים תחביריים כדי לייעל את התחביר, להסיר שמות מיותרים, הצהרות ויתירות סוג.
הארכה
בשימוש מושכל, את `extension
מנגנון ` יכול לפשט את התחביר לשימוש במחלקת סוג.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)
def lastBlock(using tc: LastBlock[A]) = tc.lastBlock(instance)
import Lib1.*, Lib2.*
println(Ethereum(lastBlock = 2).lastBlock)
println(Bitcoin().lastBlock)
שורות 28-29 שיטת הרחבה גנרית `lastBlock
` מוגדר עבור כל `A
` עם `LastBlock
` פרמטר TC בהיקף משתמע.
שורות 33-34 ההרחבה ממנפת תחביר מונחה עצמים כדי להשתמש ב-TC.
object Lib2:
import Lib1.*
trait LastBlock[A]:
def lastBlock(instance: A): Block
given LastBlock[Ethereum] with
def lastBlock(eth: Ethereum) = eth.lastBlock
given LastBlock[Bitcoin] with
def lastBlock(btc: Bitcoin) = http("http://bitcoin/last")
extension[A](instance: A)(using tc: LastBlock[A])
def lastBlock = tc.lastBlock(instance)
def penultimateBlock = tc.lastBlock(instance) - 1
import Lib1.*, Lib2.*
val eth = Ethereum(lastBlock = 2)
println(eth.lastBlock)
println(eth.penultimateBlock)
val btc = Bitcoin()
println(btc.lastBlock)
println(btc.penultimateBlock)
שורה 28, ניתן להגדיר את הפרמטר TC גם עבור כל ההרחבה כדי למנוע חזרה. בשורה 30 אנו עושים שימוש חוזר ב-TC בהרחבה כדי להגדיר `penultimateBlock
` (למרות שניתן ליישם אותו ב-`LastBlock
` תכונה ישירות)
הקסם מתרחש כאשר משתמשים ב-TC. הביטוי מרגיש הרבה יותר טבעי, נותן אשליה שהתנהגות `lastBlock
` מתבלבל עם המופע.
סוג גנרי עם TC
import Lib1.*, Lib2.*
def useLastBlock1[A](instance: A)(using LastBlock[A]) = instance.lastBlock
def useLastBlock2[A: LastBlock](instance: A) = instance.lastBlock
val eth = Ethereum(lastBlock = 2)
assert(useLastBlock1(eth) == useLastBlock2(eth))
בשורה 34 הפונקציה משתמשת ב-TC מרומז. שימו לב שאין צורך לתת שם ל-TC אם השם הזה מיותר.
דפוס ה-TC נמצא בשימוש כה נרחב שיש תחביר סוג גנרי לבטא "טיפוס עם התנהגות מרומזת". שורה 36 התחביר הוא חלופה תמציתית יותר לקודמת (שורה 34). הוא נמנע מהכרזה ספציפית על פרמטר TC המרומז ללא שם.
בכך מסתיים סעיף חוויית המפתח. ראינו כיצד הרחבות, מרומזים וכמה סוכר תחבירי יכולים לספק תחביר פחות עמוס כאשר משתמשים ב-TC ומוגדרים.
גזירה אוטומטית
הרבה ספריות Scala משתמשות ב-TC, מה שמשאיר למתכנת ליישם אותן בבסיס הקוד שלהן.
לדוגמה Circe (ספריית דה-סריאליזציה של json) משתמשת ב-TC `Encoder[T]
` ו`Decoder[T]
` עבור מתכנתים ליישם בבסיס הקוד שלהם. לאחר היישום ניתן להשתמש בכל היקף הספרייה.
ההטמעות הללו של TC הן לעתים קרובות יותר ממפים מוכווני נתונים. הם לא צריכים שום היגיון עסקי, משעממים לכתיבה, ונטל לשמור בסנכרון עם מחלקות מקרים.
במצב כזה, אותן ספריות מציעות מה שנקרא אוטומטי גזירה או חצי אוטומטי גִזרָה. ראה למשל Circe אוטומטי ו חצי אוטומטי גִזרָה. עם גזירה חצי אוטומטית המתכנת יכול להכריז על מופע של מחלקה מסוג עם תחביר מינורי כלשהו, בעוד שהגזירה האוטומטית לא מחייבת שום שינוי קוד מלבד ייבוא.
מתחת למכסה המנוע, בזמן הידור, פקודות מאקרו גנריות מסתכלות פנימה סוגים כמבנה נתונים טהור ויצירת TC[T] עבור משתמשי הספרייה.
גזירת TC באופן כללי היא נפוצה מאוד, אז Scala הציגה ארגז כלים שלם למטרה זו. שיטה זו לא תמיד מפורסמת על ידי תיעוד הספרייה למרות שזו שיטת Scala 3 להשתמש בגזירה.
object GenericLib:
trait Named[A]:
def blockchainName(instance: A): String
object Named:
import scala.deriving.*
inline final def derived[A](using inline m: Mirror.Of[A]): Named[A] =
val nameOfType: String = inline m match
case p: Mirror.ProductOf[A] => compiletime.constValue[p.MirroredLabel]
case _ => compiletime.error("Not a product")
new Named[A]:
override def blockchainName(instance: A):String = nameOfType.toLowerCase
extension[A] (instance: A)(using tc: Named[A])
def blockchainName = tc.blockchainName(instance)
import Lib1.*, GenericLib.*
case class Polkadot() derives Named
given Named[Bitcoin] = Named.derived
given Named[Ethereum] = Named.derived
println(Ethereum(lastBlock = 2).blockchainName)
println(Bitcoin().blockchainName)
println(Polkadot().blockchainName)
קו 18 TC חדש `Named
` מוצג. TC זה לא קשור לעסקי הבלוקצ'יין, למהדרין. מטרתו לתת שם לבלוקצ'יין על סמך שם מחלקת המקרים.
תחילה התמקדו בהגדרות שורות 36-38. ישנם 2 תחבירים לגזירת TC:
- בשורה 36 ניתן להגדיר את מופע ה-TC ישירות על מחלקת המקרה עם ה- `
derives
` מילת מפתח. מתחת למכסה המנוע המהדר יוצר `Named
` מופע ב`Polkadot
` חפץ נלווה. - שורה 37 ו-38, מופעי מחלקות מסוג ניתנים במחלקות קיימות מראש עם `
TC.derived
`
שורה 31 מוגדרת הרחבה גנרית (ראה סעיפים קודמים) ו`blockchainName
` משמש באופן טבעי.
ה `derives
` מילת מפתח מצפה לשיטה עם הטופס `inline def derived[T](using Mirror.Of[T]): TC[T] = ???
` שמוגדר שורה 24. לא אסביר לעומק מה הקוד עושה. בקווים כלליים:
- `
inline def
` מגדיר מאקרו - `
Mirror
` הוא חלק מארגז הכלים להסתכלות פנימית על טיפוסים. ישנם סוגים שונים של מראות, ובשורה 26 הקוד מתמקד `Product
` מראות (מחלקת מקרה היא מוצר). שורה 27, אם מתכנתים מנסים לגזור משהו שהוא לא `Product
`, הקוד לא יקמפל. - את `
Mirror
` מכיל סוגים אחרים. אחד מהם, `MirrorLabel
`, היא מחרוזת המכילה את שם הסוג. ערך זה משמש ביישום, שורה 29, של ה-`Named
` TC.
מחברי TC יכולים להשתמש בתכנות מטא כדי לספק פונקציות שמייצרות באופן כללי מופעים של TC בהינתן סוג. מתכנתים יכולים להשתמש ב-API ייעודי של ספרייה או בכלים להפקת Scala כדי ליצור מופעים עבור הקוד שלהם.
בין אם אתה צריך קוד גנרי או ספציפי כדי ליישם TC, יש פתרון לכל מצב.
סיכום כל ההטבות
- זה פותר את בעיית הביטוי
- טיפוסים חדשים יכולים ליישם התנהגות קיימת באמצעות הורשת תכונה מסורתית
- ניתן ליישם התנהגויות חדשות על סוגים קיימים
- הפרדת דאגה
- הקוד אינו מעוות וניתן למחיקה בקלות. TC מפריד בין נתונים והתנהגות, שהוא מוטו תכנות פונקציונלי.
- זה בטוח
- זה בטוח כי זה לא מסתמך על התבוננות פנימית. זה נמנע מהתאמת דפוסים גדולה הכוללת סוגים. אם אתה נתקל בעצמך כותב קוד כזה, אתה עשוי לזהות מקרה שבו דפוס TC יתאים בצורה מושלמת.
- המנגנון המרומז בטוח להידור! אם חסר מופע בזמן ההידור, הקוד לא יקמפל. אין הפתעה בזמן ריצה.
- זה מביא לפולימורפיזם אד-הוק
- פולימורפיזם אד-הוק חסר בדרך כלל בתכנות מסורתי מונחה עצמים.
- עם פולימורפיזם אד-הוק, מפתחים יכולים ליישם את אותה התנהגות עבור סוגים שונים שאינם קשורים מבלי להשתמש בהקלדת משנה מסורתית (המצמדת את הקוד)
- הזרקת תלות קלה
- ניתן לשנות מופע TC ביחס לעקרון ההחלפה של Liskov.
- כאשר לרכיב יש תלות ב-TC, ניתן להזריק בקלות TC מדומה למטרות בדיקה.
אינדיקציות נגד
כל פטיש מיועד למגוון בעיות.
מחלקות סוג מיועדות לבעיות התנהגות ואסור להשתמש בהן לירושת נתונים. השתמש בהרכב למטרה זו.
תת ההקלדה הרגילה היא פשוטה יותר. אם אתה הבעלים של בסיס הקוד ואינך שואף להרחבה, ייתכן ששיעורי הסוג יהיו מוגזמים.
לדוגמה, בליבת Scala, יש `Numeric
סוג מחלקה:
trait Numeric[T] extends Ordering[T] {
def plus(x: T, y: T): T
def minus(x: T, y: T): T
def times(x: T, y: T): T
זה באמת הגיוני להשתמש במחלקת טיפוס כזו מכיוון שהיא לא רק מאפשרת שימוש חוזר באלגוריתמים אלגבריים על טיפוסים המוטבעים ב-Scala (Int, BigInt, ...), אלא גם על טיפוסים המוגדרים על ידי המשתמש (a `ComplexNumber
` למשל).
מצד שני, הטמעה של אוספי Scala משתמשת בעיקר בהקלדת משנה במקום במחלקת סוג. עיצוב זה הגיוני מכמה סיבות:
- ה-API של האיסוף אמור להיות מלא ויציב. הוא חושף התנהגות נפוצה באמצעות תכונות שהורשתו על ידי יישומים. להיות ניתנת להרחבה היא לא מטרה מסוימת כאן.
- זה חייב להיות פשוט לשימוש. TC מוסיף תקורה נפשית למתכנת משתמש הקצה.
- TC עשויה גם לספוג תקורה קטנה בביצועים. זה עשוי להיות קריטי עבור ממשק API של אוסף.
- עם זאת, ממשק API של אוסף עדיין ניתן להרחבה באמצעות TC חדש המוגדר על ידי ספריות צד שלישי.
סיכום
ראינו ש-TC הוא דפוס פשוט שפותר בעיה גדולה. הודות לתחביר העשיר של Scala, ניתן ליישם ולהשתמש בתבנית TC בדרכים רבות. תבנית ה-TC תואמת את פרדיגמת התכנות הפונקציונלית והיא כלי נפלא לארכיטקטורה נקייה. אין כדור כסף ויש ליישם דפוס TC כאשר הוא מתאים.
מקווה שרכשת ידע בקריאת מסמך זה.
הקוד זמין ב https://github.com/jprudent/type-class-article. אנא פנה אליי אם יש לך סוג של שאלות או הערות. אתה יכול להשתמש בבעיות או בהערות קוד במאגר אם תרצה.
מהנדס תוכנה
- הפצת תוכן ויחסי ציבור מופעל על ידי SEO. קבל הגברה היום.
- PlatoData.Network Vertical Generative Ai. העצים את עצמך. גישה כאן.
- PlatoAiStream. Web3 Intelligence. הידע מוגבר. גישה כאן.
- PlatoESG. פחמן, קלינטק, אנרגיה, סביבה, שמש, ניהול פסולת. גישה כאן.
- PlatoHealth. מודיעין ביוטכנולוגיה וניסויים קליניים. גישה כאן.
- מקור: https://www.ledger.com/blog/type-classes-in-scala3-a-beginners-guide
- :יש ל
- :הוא
- :לֹא
- :איפה
- ][עמ'
- 1
- 10
- 11
- 12
- 14
- 15%
- 19
- 1998
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 35%
- 36
- 7
- 8
- 9
- a
- יכולת
- אודות
- AC
- פי
- פעולה
- ממשי
- מוסיף
- עורכי דין
- שוב
- המטרה
- אלגוריתמים
- תעשיות
- להתיר
- מאפשר
- לאורך
- כְּבָר
- גם
- חלופה
- למרות
- תמיד
- an
- ו
- אחר
- כל
- API
- יישומית
- החל
- מתאים
- ארכיטקטורה
- ארכיון
- ARE
- טיעונים
- מאמר
- AS
- היבטים
- At
- ניסיון
- מחברים
- מכני עם סלילה אוטומטית
- זמין
- לְהִמָנַע
- בחזרה
- בסיס
- מבוסס
- BE
- כי
- לפני
- מתחיל
- התנהגות
- להיות
- בֵּין
- גָדוֹל
- blockchain
- משעמם
- שניהם
- אריזה מקורית
- מביא
- רחב
- BTC
- ניטל
- עסקים
- אבל
- by
- שיחה
- נקרא
- שיחה
- CAN
- מקרה
- כִּסֵא
- שינוי
- השתנה
- בכיתה
- כיתות
- לְנַקוֹת
- קוד
- בסיס קוד
- בסיס קוד
- אוסף
- אוספים
- איך
- מגיע
- הערות
- Common
- חבר
- להשלים
- להיענות
- רְכִיב
- הרכב
- דְאָגָה
- תמציתית
- מסכם
- מסוכסך
- בלבול
- מכיל
- ליבה
- לתקן
- יכול
- לִיצוֹר
- יוצרים
- יצור
- קריטי
- נתונים
- מצהיר
- מוקדש
- לְהַגדִיר
- מוגדר
- מגדיר
- הגדרה
- הגדרות
- תלות
- עומק
- לגזור
- עיצוב
- מעוצב
- לאתר
- מפתח
- מפתחים
- DID
- אחר
- ישירות
- נשלח תוך
- צלילה
- do
- מסמך
- עושה
- לא
- לא
- באופן דינמי
- כל אחד
- להקל
- קל יותר
- בקלות
- קל
- ed
- מוטבע
- פְּגִישָׁה
- סוף
- לאכוף
- אַכִיפָה
- מהנה
- זן
- שגיאות
- ETH
- Ether (ETH)
- אֲפִילוּ
- הכל
- בדיוק
- דוגמה
- אלא
- להתקיים
- קיימים
- צפוי
- מצפה
- ניסיון
- להסביר
- מסביר
- בִּמְפוּרָשׁ
- אקספרס
- ביטוי
- הארכה
- סיומות
- נוסף
- נכשל
- תכונות
- מרגיש
- קבוע
- להתמקד
- מתמקד
- הבא
- בעד
- טופס
- החל מ-
- פונקציה
- פונקציונלי
- פונקציות
- נוסף
- לְהַשִׂיג
- צבר
- ליצור
- מייצר
- GitHub
- נתן
- נתינה
- גלוֹבָּלִי
- ראיה גלובלית
- מטרה
- מדריך
- פטיש
- יד
- קורה
- יש
- כאן
- להבליט
- מאוד
- לו
- להחזיק
- ברדס
- איך
- HTML
- http
- HTTPS
- i
- if
- אשליה
- תמונה
- ליישם
- הפעלה
- יישומים
- יושם
- מיישמים
- לייבא
- יבוא
- משופר
- in
- כולל
- ירושה
- למשל
- מקרים
- במקום
- התכוון
- אל תוך
- הציג
- מעורב
- סוגיה
- בעיות
- IT
- שֶׁלָה
- לסכן
- ג'סון
- רק
- שמור
- לדעת
- ידע
- שפה
- אחרון
- מוביל
- עזיבה
- פנקס
- עזבו
- פחות
- מנופים
- ספריות
- סִפְרִיָה
- כמו
- קו
- קווים
- לינקדין
- מקומות
- הגיון
- מגרש
- פקודות מאקרו
- עשוי
- קלי קלות
- קסם
- לתחזק
- לעשות
- עושה
- דרך
- רב
- תואם
- מאי..
- me
- מנגנון
- זכרון
- נפשי
- סתם
- meta
- שיטה
- יכול
- אכפת לי
- קטין
- ראי
- חסר
- יותר
- רוב
- בעיקר
- מוֹטוֹ
- צריך
- שם
- שם
- שמות
- טבעי
- צורך
- נחוץ
- לעולם לא
- חדש
- לא
- הערות
- אובייקט
- of
- הַצָעָה
- המיוחדות שלנו
- לעתים קרובות
- זקן
- on
- פעם
- ONE
- רק
- or
- אחר
- שלנו
- הַחוּצָה
- קווי מתאר
- יותר
- שֶׁלוֹ
- פרדיגמה
- פרמטר
- פרמטרים
- חלק
- מסוים
- צד
- לעבור
- עבר
- מעברי
- חולף
- תבנית
- בצורה מושלמת
- ביצועים
- לְחַבֵּר
- אפלטון
- מודיעין אפלטון
- אפלטון נתונים
- אנא
- אפשרי
- מעשי
- לְהַעֲדִיף
- מוצג
- קודם
- קוֹדֶם
- עקרון
- בעיה
- בעיות
- המוצר
- תָכְנִית
- מְתַכנֵת
- מתכנתים
- תכנות
- תָקִין
- לספק
- מטרה
- למטרות
- גם
- מכניס
- שאלות
- רכס
- במקום
- לְהַגִיעַ
- הגיע
- קריאה
- בֶּאֱמֶת
- טעם
- לסכם
- מתכון
- לסמוך
- להשאר
- לזכור
- הסרת
- החליף
- מאגר
- נציגות
- מייצג
- נפתרה
- כבוד
- שימוש חוזר
- עשיר
- כלל
- כללי
- s
- בטוח
- בבטחה
- בְּטִיחוּת
- טוֹבָה
- סם
- אותו
- סולם
- סצינה
- היקף
- סעיף
- סעיפים
- לִרְאוֹת
- לראות
- תחושה
- סט
- כמה
- קצר
- צריך
- כסף
- פָּשׁוּט
- לפשט
- מפשט
- יחיד
- מצב
- קטן
- So
- תוכנה
- מוצק
- פִּתָרוֹן
- נפתר
- פותר
- כמה
- משהו
- מָקוֹר
- קוד מקור
- מֶרחָב
- מדבר
- מיוחד
- ספציפי
- במיוחד
- יציב
- התחלה
- הצהרה
- שלב
- צעדים
- עוד
- פשוט
- לייעל
- מחרוזת
- מִבְנֶה
- כזה
- סוכר
- להציע
- כדלקמן
- אמור
- בטוח
- הפתעה
- מתג
- סינכרון.
- תחביר
- מערכת
- T
- לקחת
- המשימות
- אומר
- בדיקות
- מֵאֲשֶׁר
- תודה
- זֶה
- השמיים
- המקור
- שֶׁלָהֶם
- אותם
- שם.
- הֵם
- דבר
- דברים
- לחשוב
- שְׁלִישִׁי
- זֶה
- אלה
- אם כי?
- דרך
- זמן
- ל
- כלי
- ארגז כלים
- כלים
- נגע
- מסורתי
- ניסיתי
- באמת
- לנסות
- שתיים
- סוג
- סוגים
- נדיר
- תחת
- ייחודי
- ללא שם
- עד
- לא נגע
- לא בשימוש
- על
- נוֹהָג
- להשתמש
- מְשׁוּמָשׁ
- משתמש
- משתמשים
- שימושים
- באמצעות
- כרגיל
- בְּדֶרֶך כְּלַל
- ערך
- שונים
- בָּקִי
- מאוד
- רוצה
- היה
- דֶרֶך..
- דרכים
- we
- מה
- מה
- מתי
- ואילו
- אשר
- מי
- כל
- למה
- באופן נרחב
- יצטרך
- בחוכמה
- עם
- לְלֹא
- היה
- לכתוב
- כתיבה
- טעות
- עוד
- אתה
- עצמך
- זפירנט