کلاس های تایپ در Scala3: راهنمای مبتدی | دفتر کل

کلاس های تایپ در Scala3: A Beginner's Guide | دفتر کل

گره منبع: 3028113

این سند برای توسعه دهنده مبتدی Scala3 در نظر گرفته شده است که قبلاً به نثر Scala مسلط است، اما در مورد همه اینها متحیر است.implicits` و صفات پارامتر شده در کد.

این سند چرایی، چگونه، کجا و چه زمانی را توضیح می دهد کلاس های نوع (TC).

پس از خواندن این سند، توسعه دهنده مبتدی Scala3 دانش کاملی برای استفاده و بررسی کد منبع آن به دست خواهد آورد. زیاد از کتابخانه های اسکالا و شروع به نوشتن کد اسکالا اصطلاحی کنید.

بیایید با دلیل…

مشکل بیان

در 1998، فیلیپ وادلر بیان کرد که "مشکل عبارت نام جدیدی برای یک مشکل قدیمی است". مشکل توسعه پذیری نرم افزار است. طبق نوشته آقای Wadler، راه حل مسئله بیان باید با قوانین زیر مطابقت داشته باشد:

  • قانون 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 مشکل است. ما سعی کردیم آن را با تعریف خودمان از چندشکلی و ترفند «بسط» پیاده سازی کنیم. و این عجیب بود

یک قطعه گم شده به نام وجود دارد چند شکلی ad-hoc: توانایی ارسال ایمن اجرای یک رفتار بر اساس یک نوع، هر جا که رفتار و نوع آن تعریف شده باشد. را وارد کنید کلاس تایپ الگوی.

الگوی کلاس Type

دستور العمل الگوی کلاس Type (به اختصار TC) دارای 3 مرحله است. 

  1. یک رفتار جدید را تعریف کنید
  2. رفتار را اجرا کنید
  3. از رفتار استفاده کن

در بخش بعدی، الگوی TC را به ساده ترین شکل پیاده سازی می کنم. این پرمخاطب، درهم و غیرعملی است. اما صبر کنید، این اخطارها گام به گام در سند رفع خواهند شد.

1. یک رفتار جدید تعریف کنید
اسکالا

object Lib2:
  import Lib1.*

  trait LastBlock[A]:
    def lastBlock(instance: A): Block

`Lib1` بار دیگر دست نخورده باقی مانده است.

رفتار جدید is TC توسط این ویژگی تحقق یافت. توابع تعریف شده در صفت راهی برای اعمال برخی از جنبه های آن رفتار است.

پارامتر `A` نشان دهنده نوعی است که ما می خواهیم رفتار را به آن اعمال کنیم که زیرگروه های ` هستندBlockchainدر مورد ما

چند نکته:

  • در صورت نیاز، نوع پارامتر شده `A` می تواند توسط سیستم نوع اسکالا محدودتر شود. به عنوان مثال، ما می توانیم « را اجرا کنیم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«چسب بین نمایش (الف واقعی) و رفتار آن است. داده ها و رفتار از هم جدا هستند، این همان چیزی است که برنامه نویسی کاربردی از آن حمایت می کند.

بحث

بیایید قواعد مسئله بیان را مرور کنیم:

  • قانون 1: اجازه اجرای رفتارهای موجود  اعمال شود کلاس های جدید
  • قانون 2: اجازه اجرای رفتارهای جدید اعمال شود کلاس های موجود
  • قانون 3: نباید به خطر بیفتد ایمنی نوع
  • قانون 4: نباید نیاز به کامپایل مجدد داشته باشد کد موجود

قانون 1 را می توان با چند شکلی زیرگروهی حل کرد.

الگوی TC که به تازگی ارائه شده است (نگاه کنید به اسکرین شات قبلی) قانون 2 را حل می کند. از نوع امن است (قانون 3) و ما هرگز به آن دست نزدیم.Lib1(قاعده 4). 

اما استفاده از آن به چند دلیل غیرعملی است:

  • در خطوط 33-34 باید رفتار را به طور صریح در امتداد نمونه آن منتقل کنیم. این یک سربار اضافی است. فقط باید بنویسیمuseLastBlock(Bitcoin())`.
  • خط 31 نحو غیر معمول است. ما ترجیح می دهیم که مختصرتر و شی گراتر بنویسیمinstance.lastBlock()` بیانیه

بیایید برخی از ویژگی های Scala را برای استفاده عملی از TC برجسته کنیم. 

تجربه توسعه دهنده پیشرفته

Scala دارای مجموعه ای منحصر به فرد از ویژگی ها و قندهای نحوی است که TC را به یک تجربه واقعا لذت بخش برای توسعه دهندگان تبدیل می کند.

ضمنی

دامنه ضمنی یک محدوده ویژه است که در زمان کامپایل حل می شود که در آن فقط یک نمونه از یک نوع معین می تواند وجود داشته باشد. 

یک برنامه یک نمونه را در محدوده ضمنی با ` قرار می دهدgivenکلمه کلیدی یا یک برنامه می تواند یک نمونه را از محدوده ضمنی با کلمه کلیدی «بازیابی کندusing`.

دامنه ضمنی در زمان کامپایل حل می شود، روشی برای تغییر آن به صورت پویا در زمان اجرا وجود دارد. اگر برنامه کامپایل شود، دامنه ضمنی حل می شود. در زمان اجرا، نمی‌توان نمونه‌های ضمنی از دست رفته را در جایی که از آنها استفاده می‌شود، داشت. تنها سردرگمی ممکن ممکن است ناشی از استفاده از مثال ضمنی اشتباه باشد، اما این مسئله برای موجودی بین صندلی و صفحه کلید باقی مانده است.

با دامنه جهانی متفاوت است زیرا: 

  1. به صورت متنی حل شده است. دو مکان از یک برنامه می توانند از یک نمونه از یک نوع معین در محدوده ضمنی استفاده کنند، اما این دو نمونه ممکن است متفاوت باشند.
  2. در پشت صحنه، کد تابع آرگومان‌های ضمنی را ارسال می‌کند تا به استفاده ضمنی برسد. از فضای حافظه جهانی استفاده نمی کند.

برگردیم به کلاس تایپ! بیایید دقیقاً همین مثال را در نظر بگیریم.

اسکالا

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کامپایلر به او می گوید.

Scala ضمنی کار عبور از نمونه های کلاس همراه با نمونه هایی از رفتار آنها را آسان می کند.

قندهای ضمنی

خط 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 باشد، مگر اینکه به طور معمولی گلف کد می‌زنید.

اسکالا قندهای نحوی را برای ساده‌سازی نحو ارائه می‌کند و نام‌گذاری، اعلان و افزونگی غیر ضروری را حذف می‌کند.

توسعه

عاقلانه استفاده می شود، «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، نحو کمتری به هم ریخته ارائه دهند.

استخراج خودکار

بسیاری از کتابخانه‌های اسکالا از TC استفاده می‌کنند و برنامه‌نویس را مجبور می‌کند تا آن‌ها را در پایه کد خود پیاده‌سازی کند.

برای مثال Circe (یک کتابخانه سریال‌زدایی json) از TC ` استفاده می‌کندEncoder[T]`و`Decoder[T]برای برنامه نویسان برای پیاده سازی در پایگاه کد خود. پس از پیاده سازی، می توان از کل محدوده کتابخانه استفاده کرد. 

این پیاده‌سازی‌های TC بیشتر از مواقع هستند نقشه برداران داده گرا. آنها به هیچ منطق تجاری نیاز ندارند، برای نوشتن کسل کننده هستند، و باری برای همگام سازی با کلاس های موردی دارند.

در چنین شرایطی، آن کتابخانه ها چیزی را ارائه می دهند که به آن گفته می شود اتوماتیک اشتقاق یا نیمه خود کار استخراج. به عنوان مثال Circe را ببینید اتوماتیک و نیمه خود کار استخراج. با اشتقاق نیمه خودکار، برنامه نویس می تواند نمونه ای از یک کلاس نوع را با مقداری نحو جزئی اعلام کند، در حالی که اشتقاق خودکار نیازی به تغییر کد ندارد به جز واردات.

در زیر هود، در زمان کامپایل، ماکروهای عمومی درون‌نگری می‌شوند انواع به عنوان ساختار داده خالص و ایجاد یک TC[T] برای کاربران کتابخانه. 

استخراج یک TC به طور کلی بسیار رایج است، بنابراین اسکالا جعبه ابزار کاملی را برای این منظور معرفی کرد. این روش همیشه توسط اسناد کتابخانه ای تبلیغ نمی شود، اگرچه این روش 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 وجود دارد:

  1. خط 36 نمونه TC را می توان مستقیماً روی کلاس case با « تعریف کردderivesکلمه کلیدی در زیر هود، کامپایلر یک ` داده شده را تولید می کندNamed"مثال در"Polkadot` شیء همراه.
  2. خط 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، از ` استفاده می شودNamedTC.

نویسندگان TC می توانند از برنامه نویسی متا برای ارائه توابعی استفاده کنند که به طور کلی نمونه هایی از TC را با یک نوع خاص تولید می کنند. برنامه نویسان می توانند از API کتابخانه اختصاصی یا ابزار استخراج Scala برای ایجاد نمونه هایی برای کد خود استفاده کنند.

چه برای پیاده سازی یک TC به کد عمومی یا خاص نیاز داشته باشید، برای هر موقعیتی راه حلی وجود دارد. 

خلاصه تمام مزایا

  • مشکل بیان را حل می کند
    • انواع جدید می توانند رفتار موجود را از طریق وراثت صفت سنتی پیاده سازی کنند
    • رفتارهای جدید را می توان بر روی انواع موجود پیاده سازی کرد
  • جداسازی نگرانی
    • کد مخدوش نیست و به راحتی قابل حذف است. یک TC داده ها و رفتار را از هم جدا می کند، که یک شعار برنامه نویسی کاربردی است.
  • امن است
    • از نوع ایمن است زیرا به درون نگری متکی نیست. از تطبیق الگوهای بزرگ شامل انواع جلوگیری می کند. اگر با نوشتن چنین کدی مواجه شدید، ممکن است موردی را تشخیص دهید که الگوی TC کاملاً مناسب باشد.
    • مکانیسم ضمنی کامپایل امن است! اگر یک نمونه در زمان کامپایل گم شده باشد، کد کامپایل نمی شود. بدون تعجب در زمان اجرا.
  • چندشکلی موقتی را به ارمغان می آورد
    • چند شکلی ad hoc معمولاً در برنامه نویسی شی گرا سنتی وجود ندارد.
    • با چند شکلی ad-hoc، توسعه دهندگان می توانند رفتار یکسانی را برای انواع مختلف نامرتبط بدون استفاده از تایپ فرعی سنتی (که کد را جفت می کند) پیاده سازی کنند.
  • تزریق وابستگی آسان شد
    • یک نمونه 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به عنوان مثال).

از سوی دیگر، پیاده سازی مجموعه های اسکالا بیشتر از تایپ فرعی به جای کلاس نوع استفاده می کند. این طراحی به چند دلیل منطقی است:

  • مجموعه API قرار است کامل و پایدار باشد. این رفتار مشترک را از طریق صفات به ارث رسیده توسط پیاده سازی ها نشان می دهد. در اینجا بسیار توسعه پذیر بودن هدف خاصی نیست.
  • استفاده از آن باید ساده باشد. TC یک سربار ذهنی به برنامه نویس کاربر نهایی اضافه می کند.
  • TC همچنین ممکن است سربار کمی را در عملکرد متحمل شود. این ممکن است برای یک مجموعه API حیاتی باشد.
  • اگرچه، مجموعه API هنوز از طریق TC جدید تعریف شده توسط کتابخانه های شخص ثالث قابل گسترش است.

نتیجه

ما دیدیم که TC یک الگوی ساده است که یک مشکل بزرگ را حل می کند. به لطف نحو غنی Scala، الگوی TC را می توان به روش های مختلفی پیاده سازی و استفاده کرد. الگوی TC مطابق با الگوی برنامه نویسی عملکردی است و ابزاری شگفت انگیز برای یک معماری تمیز است. هیچ گلوله نقره ای وجود ندارد و الگوی TC باید در زمان مناسب اعمال شود.

امیدوارم با خواندن این سند دانش کسب کرده باشید. 

کد در دسترس است https://github.com/jprudent/type-class-article. لطفا در صورت داشتن هرگونه سوال یا نکته با من تماس بگیرید. در صورت تمایل می توانید از مسائل یا نظرات کد در مخزن استفاده کنید.


جروم محتاط

مهندس نرمافزار

تمبر زمان:

بیشتر از دفتر کل