เอกสารนี้มีไว้สำหรับนักพัฒนา Scala3 มือใหม่ที่เชี่ยวชาญเรื่องร้อยแก้ว Scala อยู่แล้ว แต่ยังรู้สึกงุนงงกับ `implicits
` และลักษณะที่เป็นพารามิเตอร์ในโค้ด
เอกสารนี้จะอธิบายว่าทำไม อย่างไร ที่ไหน และเมื่อใด ประเภทคลาส (TC).
หลังจากอ่านเอกสารนี้แล้ว นักพัฒนา Scala3 ระดับเริ่มต้นจะได้รับความรู้ที่ชัดเจนในการใช้งานและเจาะลึกซอร์สโค้ดของ มาก ของไลบรารี Scala และเริ่มเขียนโค้ด Scala สำนวน
มาเริ่มกันว่าทำไม…
ปัญหาการแสดงออก
ใน 1998, ฟิลิป วัดเลอร์ กล่าว ว่า “ปัญหาการแสดงออกเป็นชื่อใหม่ของปัญหาเก่า” มันเป็นปัญหาของการขยายซอฟต์แวร์ ตามการเขียนของมิสเตอร์ Wadler การแก้ปัญหานิพจน์ต้องเป็นไปตามกฎต่อไปนี้:
- กฎข้อที่ 1: อนุญาตให้มีการดำเนินการของ พฤติกรรมที่มีอยู่ (คิดถึงลักษณะสกาล่า) มาประยุกต์ใช้ การนำเสนอใหม่ (คิดถึงคลาสเคส)
- กฎข้อที่ 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 นี้ยุ่งยาก เราพยายามใช้มันด้วยคำจำกัดความของความหลากหลายและเคล็ดลับ "ส่วนขยาย" ของเราเอง และนั่นก็แปลก
มีชิ้นส่วนที่หายไปเรียกว่า ความหลากหลายเฉพาะกิจ: ความสามารถในการจัดส่งการนำพฤติกรรมไปใช้ตามประเภทได้อย่างปลอดภัย ไม่ว่าจะกำหนดพฤติกรรมและประเภทไว้ที่ใดก็ตาม ป้อน ประเภทคลาส ลวดลาย
รูปแบบคลาสประเภท
สูตรรูปแบบ 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
` มันคอมไพเลอร์บอกเขา
โดยนัยของ 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 อย่างเหมาะสม เว้นแต่ว่าคุณจะเขียนโค้ดกอล์ฟแบบไม่ได้ตั้งใจ
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 เหล่านั้นมีบ่อยกว่าปกติ ผู้ทำแผนที่เชิงข้อมูล. พวกเขาไม่ต้องการตรรกะทางธุรกิจ น่าเบื่อในการเขียน และเป็นภาระที่ต้องรักษาให้สอดคล้องกับคลาสเคส
ในสถานการณ์เช่นนี้ ห้องสมุดเหล่านั้นจะนำเสนอสิ่งที่เรียกว่า อัตโนมัติ ที่มาหรือ กึ่งอัตโนมัติ ที่มา ดูตัวอย่าง เซอร์ซี อัตโนมัติ และ กึ่งอัตโนมัติ ที่มา ด้วยการรับมาแบบกึ่งอัตโนมัติ โปรแกรมเมอร์สามารถประกาศอินสแตนซ์ของคลาสประเภทที่มีไวยากรณ์ย่อยบางส่วนได้ ในขณะที่การได้มาแบบอัตโนมัติไม่จำเป็นต้องมีการแก้ไขโค้ดใดๆ ยกเว้นการนำเข้า
ภายใต้ประทุนในเวลารวบรวมมาโครทั่วไปจะพิจารณาใคร่ครวญ ชนิด เป็นโครงสร้างข้อมูลล้วนๆ และสร้าง 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 โดยทั่วไปตามประเภทได้ โปรแกรมเมอร์สามารถใช้ API ไลบรารีเฉพาะหรือเครื่องมือที่ได้รับ Scala เพื่อสร้างอินสแตนซ์สำหรับโค้ดของตน
ไม่ว่าคุณจะต้องใช้โค้ดทั่วไปหรือโค้ดเฉพาะเพื่อใช้งาน TC ก็มีวิธีแก้ไขสำหรับแต่ละสถานการณ์
สรุปสิทธิประโยชน์ทั้งหมด
- มันแก้ปัญหาการแสดงออก
- ประเภทใหม่สามารถนำพฤติกรรมที่มีอยู่ไปใช้ผ่านการสืบทอดลักษณะแบบดั้งเดิม
- ลักษณะการทำงานใหม่สามารถนำไปใช้กับประเภทที่มีอยู่ได้
- แยกความกังวล
- รหัสไม่เสียหายและลบได้ง่าย TC แยกข้อมูลและพฤติกรรม ซึ่งเป็นคำขวัญการเขียนโปรแกรมเชิงฟังก์ชัน
- มันปลอดภัย
- เป็นประเภทที่ปลอดภัยเพราะไม่ต้องพึ่งพาวิปัสสนา หลีกเลี่ยงการจับคู่รูปแบบใหญ่ที่เกี่ยวข้องกับประเภทต่างๆ หากคุณพบว่าตัวเองกำลังเขียนโค้ดดังกล่าว คุณอาจตรวจพบกรณีที่รูปแบบ TC เหมาะสมที่สุด
- กลไกโดยนัยนั้นรวบรวมได้อย่างปลอดภัย! หากอินสแตนซ์หายไปในขณะคอมไพล์ โค้ดจะไม่คอมไพล์ ไม่แปลกใจเลยที่รันไทม์
- มันนำมาซึ่งความหลากหลายเฉพาะกิจ
- Ad hoc polymorphism มักจะหายไปในการเขียนโปรแกรมเชิงวัตถุแบบดั้งเดิม
- ด้วย ad-hoc polymorphism นักพัฒนาสามารถใช้พฤติกรรมเดียวกันสำหรับประเภทต่างๆ ที่ไม่เกี่ยวข้องกัน โดยไม่ต้องใช้การพิมพ์ย่อยแบบดั้งเดิม (ซึ่งจับคู่โค้ด)
- การฉีดพึ่งพาทำได้ง่าย
- อินสแตนซ์ TC สามารถเปลี่ยนแปลงได้ตามหลักการทดแทน Liskov
- เมื่อส่วนประกอบต้องพึ่งพา TC ก็สามารถฉีด TC จำลองเพื่อการทดสอบได้อย่างง่ายดาย
ตัวบ่งชี้ที่เคาน์เตอร์
ค้อนทุกตัวได้รับการออกแบบมาเพื่อแก้ไขปัญหาต่างๆ
คลาสประเภทใช้สำหรับปัญหาด้านพฤติกรรมและต้องไม่ใช้สำหรับการสืบทอดข้อมูล ใช้การจัดองค์ประกอบเพื่อจุดประสงค์นั้น
การพิมพ์ย่อยตามปกติจะตรงไปตรงมามากกว่า หากคุณเป็นเจ้าของ Code Base และไม่ได้มุ่งเป้าไปที่ความสามารถในการขยาย คลาสประเภทอาจจะเกินความจำเป็น
ตัวอย่างเช่น ในแกน 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 เพิ่มพลังให้กับตัวเอง เข้าถึงได้ที่นี่.
- เพลโตไอสตรีม. Web3 อัจฉริยะ ขยายความรู้ เข้าถึงได้ที่นี่.
- เพลโตESG. คาร์บอน, คลีนเทค, พลังงาน, สิ่งแวดล้อม แสงอาทิตย์, การจัดการของเสีย. เข้าถึงได้ที่นี่.
- เพลโตสุขภาพ เทคโนโลยีชีวภาพและข่าวกรองการทดลองทางคลินิก เข้าถึงได้ที่นี่.
- ที่มา: 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
- ประยุกต์
- ใช้
- เหมาะสม
- สถาปัตยกรรม
- เอกสารเก่า
- เป็น
- ข้อโต้แย้ง
- บทความ
- AS
- ด้าน
- At
- ความพยายาม
- ผู้เขียน
- อัตโนมัติ
- ใช้ได้
- หลีกเลี่ยง
- กลับ
- ฐาน
- ตาม
- BE
- เพราะ
- ก่อน
- Beginner
- พฤติกรรม
- กำลัง
- ระหว่าง
- ใหญ่
- blockchain
- เจาะ
- ทั้งสอง
- กล่อง
- นำ
- กว้าง
- BTC
- ภาระ
- ธุรกิจ
- แต่
- by
- โทรศัพท์
- ที่เรียกว่า
- ผู้เรียก
- CAN
- กรณี
- เก้าอี้
- เปลี่ยนแปลง
- การเปลี่ยนแปลง
- ชั้น
- ชั้นเรียน
- ปลาเดยส์
- รหัส
- ฐานรหัส
- codebase
- ชุด
- คอลเลกชัน
- อย่างไร
- มา
- ความคิดเห็น
- ร่วมกัน
- สหาย
- สมบูรณ์
- ปฏิบัติตาม
- ส่วนประกอบ
- ส่วนประกอบ
- กังวล
- กระชับ
- สรุป
- ยุบ
- ความสับสน
- มี
- แกน
- แก้ไข
- ได้
- สร้าง
- การสร้าง
- สัตว์
- วิกฤติ
- ข้อมูล
- ประกาศ
- ทุ่มเท
- กำหนด
- กำหนด
- กำหนด
- คำนิยาม
- คำจำกัดความ
- การอยู่ที่
- ความลึก
- ได้มา
- ออกแบบ
- ได้รับการออกแบบ
- ตรวจจับ
- ผู้พัฒนา
- นักพัฒนา
- DID
- ต่าง
- โดยตรง
- ส่งไป
- การดำน้ำ
- do
- เอกสาร
- ทำ
- ไม่
- Dont
- แบบไดนามิก
- แต่ละ
- ความสะดวก
- ง่ายดาย
- อย่างง่ายดาย
- ง่าย
- ed
- ที่ฝัง
- พบ
- ปลาย
- บังคับใช้
- การบังคับใช้
- พอใจ
- เข้าสู่
- ข้อผิดพลาด
- ETH
- อีเธอร์ (ETH)
- แม้
- ทุกอย่าง
- เผง
- ตัวอย่าง
- ยกเว้น
- มีอยู่
- ที่มีอยู่
- ที่คาดหวัง
- คาดว่า
- ประสบการณ์
- อธิบาย
- อธิบาย
- อย่างชัดเจน
- ด่วน
- การแสดงออก
- นามสกุล
- ส่วนขยาย
- พิเศษ
- ล้มเหลว
- คุณสมบัติ
- รู้สึก
- การแก้ไข
- โฟกัส
- มุ่งเน้นไปที่
- ดังต่อไปนี้
- สำหรับ
- ฟอร์ม
- ราคาเริ่มต้นที่
- ฟังก์ชัน
- การทำงาน
- ฟังก์ชั่น
- ต่อไป
- ได้รับ
- ที่ได้รับ
- สร้าง
- สร้าง
- GitHub
- กำหนด
- ให้
- เหตุการณ์ที่
- ขอบเขตทั่วโลก
- เป้าหมาย
- ให้คำแนะนำ
- ค้อน
- มือ
- ที่เกิดขึ้น
- มี
- โปรดคลิกที่นี่เพื่ออ่านรายละเอียดเพิ่มเติม
- เน้น
- อย่างสูง
- พระองค์
- ถือ
- กระโปรงหน้ารถ
- สรุป ความน่าเชื่อถือของ Olymp Trade?
- HTML
- ที่ http
- HTTPS
- i
- if
- ภาพมายา
- ภาพ
- การดำเนินการ
- การดำเนินงาน
- การใช้งาน
- การดำเนินการ
- การดำเนินการ
- นำเข้า
- การนำเข้า
- การปรับปรุง
- in
- รวมทั้ง
- มรดก
- ตัวอย่าง
- อินสแตนซ์
- แทน
- ตั้งใจว่า
- เข้าไป
- แนะนำ
- ที่เกี่ยวข้องกับ
- ปัญหา
- ปัญหา
- IT
- ITS
- เป็นภัยต่อ
- JSON
- เพียงแค่
- เก็บ
- ทราบ
- ความรู้
- ภาษา
- ชื่อสกุล
- นำไปสู่
- การออกจาก
- บัญชีแยกประเภท
- ซ้าย
- น้อยลง
- ยกระดับ
- ห้องสมุด
- ห้องสมุด
- กดไลก์
- Line
- เส้น
- วันหยุด
- ตรรกะ
- Lot
- แมโคร
- ทำ
- Made Easy
- มายากล
- เก็บรักษา
- ทำ
- ทำให้
- ลักษณะ
- หลาย
- การจับคู่
- อาจ..
- me
- กลไก
- หน่วยความจำ
- จิต
- Mers
- Meta
- วิธี
- อาจ
- ใจ
- ผู้เยาว์
- กระจก
- หายไป
- ข้อมูลเพิ่มเติม
- มากที่สุด
- ส่วนใหญ่
- ภาษิต
- ต้อง
- ชื่อ
- ที่มีชื่อ
- การตั้งชื่อ
- โดยธรรมชาติ
- จำเป็นต้อง
- จำเป็น
- ไม่เคย
- ใหม่
- ไม่
- หมายเหตุ
- วัตถุ
- of
- เสนอ
- เสนอ
- มักจะ
- เก่า
- on
- ครั้งเดียว
- ONE
- เพียง
- or
- อื่นๆ
- ของเรา
- ออก
- โครงร่าง
- เกิน
- ของตนเอง
- ตัวอย่าง
- พารามิเตอร์
- พารามิเตอร์
- ส่วนหนึ่ง
- ในสิ่งที่สนใจ
- พรรค
- ส่ง
- ผ่าน
- ผ่าน
- ที่ผ่านไป
- แบบแผน
- อย่างสมบูรณ์
- การปฏิบัติ
- ชิ้น
- เพลโต
- เพลโตดาต้าอินเทลลิเจนซ์
- เพลโตดาต้า
- กรุณา
- เป็นไปได้
- ประยุกต์
- ชอบ
- นำเสนอ
- ก่อน
- ก่อนหน้านี้
- หลัก
- ปัญหา
- ปัญหาที่เกิดขึ้น
- ผลิตภัณฑ์
- โครงการ
- โปรแกรมเมอร์
- โปรแกรมเมอร์
- การเขียนโปรแกรม
- เหมาะสม
- ให้
- วัตถุประสงค์
- วัตถุประสงค์
- ใส่
- ทำให้
- คำถาม
- พิสัย
- ค่อนข้าง
- มาถึง
- ถึง
- การอ่าน
- จริงๆ
- เหตุผล
- ปะยางรถ
- สูตร
- วางใจ
- ยังคง
- จำ
- ลบ
- แทนที่
- กรุ
- การแสดง
- แสดงให้เห็นถึง
- ได้รับการแก้ไข
- เคารพ
- นำมาใช้ใหม่
- รวย
- กฎ
- กฎระเบียบ
- s
- ปลอดภัย
- อย่างปลอดภัย
- ความปลอดภัย
- ประโยชน์
- แซม
- เดียวกัน
- สกาล่า
- ฉาก
- ขอบเขต
- Section
- ส่วน
- เห็น
- เห็น
- ความรู้สึก
- ชุด
- หลาย
- สั้น
- น่า
- เงิน
- ง่าย
- ลดความซับซ้อน
- ลดความซับซ้อน
- เดียว
- สถานการณ์
- เล็ก
- So
- ซอฟต์แวร์
- ของแข็ง
- ทางออก
- แก้ไข
- แก้ปัญหา
- บาง
- บางสิ่งบางอย่าง
- แหล่ง
- รหัสแหล่งที่มา
- ช่องว่าง
- การพูด
- พิเศษ
- โดยเฉพาะ
- เฉพาะ
- มั่นคง
- เริ่มต้น
- คำแถลง
- ขั้นตอน
- ขั้นตอน
- ยังคง
- ซื่อตรง
- เพรียวลม
- เชือก
- โครงสร้าง
- อย่างเช่น
- น้ำตาล
- แนะนำ
- สูท
- ควร
- แน่ใจ
- แปลกใจ
- สวิตซ์
- ซิงค์.
- วากยสัมพันธ์
- ระบบ
- T
- เอา
- งาน
- บอก
- การทดสอบ
- กว่า
- ขอบคุณ
- ที่
- พื้นที่
- ที่มา
- ของพวกเขา
- พวกเขา
- ที่นั่น
- พวกเขา
- สิ่ง
- สิ่ง
- คิด
- ที่สาม
- นี้
- เหล่านั้น
- แต่?
- ตลอด
- เวลา
- ไปยัง
- เครื่องมือ
- กล่องเครื่องมือ
- เครื่องมือ
- สัมผัส
- แบบดั้งเดิม
- พยายาม
- อย่างแท้จริง
- ลอง
- สอง
- ชนิด
- ชนิด
- ผิดปกติ
- ภายใต้
- เป็นเอกลักษณ์
- ไม่มีชื่อ
- จนกระทั่ง
- มิได้ถูกแตะต้อง
- ไม่ได้ใช้
- เมื่อ
- การใช้
- ใช้
- มือสอง
- ผู้ใช้งาน
- ผู้ใช้
- ใช้
- การใช้
- ตามปกติ
- มักจะ
- ความคุ้มค่า
- ต่างๆ
- มีประสบการณ์
- มาก
- ต้องการ
- คือ
- ทาง..
- วิธี
- we
- อะไร
- ความหมายของ
- เมื่อ
- แต่ทว่า
- ที่
- WHO
- ทั้งหมด
- ทำไม
- อย่างกว้างขวาง
- จะ
- ชาญฉลาด
- กับ
- ไม่มี
- จะ
- เขียน
- การเขียน
- ผิด
- ยัง
- เธอ
- ของคุณ
- ด้วยตัวคุณเอง
- ลมทะเล