Scala là gì? Thông tin về ngôn ngữ lập trình bậc cao Scala
Scala ra đời năm 2003 đã trở thành một ngôn ngữ Server bậc cao được yêu thích trên toàn thế giới. Scala mang sức mạnh của lập trình hướng đối tượng được kế thừa và phát triển từ Java cộng với những tính năng của một ngôn ngữ lập trình hàm hiện đại.
Scala là một ngôn ngữ chặt chẽ và rất "professional" khi phát triển những hệ thống cần scale lớn, đồng thời cũng rất "quyến rũ" đối với hacker. Tuy vậy Scala cũng là một ngôn ngữ lớn với những khái niệm tương đối khó. Hy vọng loạt bài viết của Bizfly Cloud lần này sẽ điểm qua được những điểm nổi bật để giúp bạn có cái nhìn tổng quan về Scala.
Tại sao lại chọn Scala?
Scala là ngôn ngữ hướng đối tượng chuẩn mực.Tại sao lại chuẩn mực ? Vì Scala kế thừa những tính năng hướng đối tượng từ Java, ngôn ngữ "mainstream" về hướng đối tượng, đồng thời đã tiến hóa thêm một bậc.
- Mixin viết bằng Trait
- Structural subtyping
- Variance
- Self type annotation
- Kế thừa class kiểu implicit
- Xác lập quyền truy cập rõ ràng với từ khóa kiểu private[this]
- Tất cả các giá trị có thể sử dụng như là một object
Nếu bạn có nền tảng tốt về Java thì sẽ thấy quen thuộc với một số tính năng kể trên. Nhưng hãy đợi phần cụ thể nhé.
Scala là ngôn ngữ lập trình hàm bậc cao
Có rất nhiều ngôn ngữ lập trình hàm như Lisp, Haskell,... từng tồn tại. Trong số đó, Scala được xếp là một ngôn ngữ lập trình hàm bậc cao bởi những lý do sau đây:
- Hỗ trợ Case class
- Hỗ trợ Pattern matching
- Class định nghĩa với implicit parameter
- Monad
- Higher kinded type
Mỗi tính năng ở trên sẽ được phát triển thành nhiều tính năng nhỏ. Nếu bạn chưa dùng ngôn ngữ lập trình hàm bao giờ thì có thể sẽ hơi choáng ngợp khi nghe đến những tính năng này. Đừng lo, chúng ta sẽ làm quen dần dần!
Scala tương thích với Java
Một ưu điểm lớn của Scala là độ tương thích với Java. Scala có thể gọi và sử dụng bất cứ thư viện Java nào, hơn nữa bản thân code Scala khi biên dịch ra cũng sẽ cùng mã bytecode của JVM. Như vậy, nguồn thư viện và kiến thức khổng lồ của ngành công nghiệp sẽ không bị lãng phí mà có thể tùy ý sử dụng theo chọn lựa.
Scala ưu việt khi xử lý tính toán song song, phân tán và bất đồng bộ
Scala có bộ thư viện chuẩn Future để làm tính toán bất đồng bộ. Đồng thời Web framework Play cũng dưa trên bộ thư viện Netty.
Akka lại là một thư viện rất mạnh nữa để xử lý tính toán phân tán và tính toán song song. Trong Akka tồn tại khái niệm Actor như là hạt nhân cơ bản. giao tiếp với nhau thông qua message.
Trait và lập trình hướng đối tượng nâng cao
Chương trình phần mềm phát triển cho các hệ thống lớn và lâu đời quan trọng nhất là tính đóng gói và cách lắp ghép các đơn vị gói đó lại với nhau. Khi lập trình hướng đối tượng ra đời, tính đóng gói được phát triển thành một lý thuyết có chiều sâu với nhiều pattern đi kèm. Đối với lập trình hướng đối tượng hay lập trình hàm thì tính đóng gói vẫn là những khái niệm cơ bản và quan trọng nhất. Và một trong số đó là Trait.
Trait trong Scala có những điểm khác biệt chủ yếu với class như sau:
- Một trait hoặc class có thể kế thừa nhiều trait khác nhau. Tính năng này được gọi là Mixin hoặc Multiple inheritance.
- Không thể khởi tạo trực tiếp instance từ trait
- Không thể nhận parameter (constructor parameter) như class
Multiple inheritance
Dưới đây là ví dụ về multiple inheritance:
trait Saiyan
trait Namek
class Human
class God
// OK
class SuperHero extends Human with Saiyan with Namek
// Compile error
class SemiGod extends Human with God
Như bạn thấy ở dòng cuối, một class không thể kế thừa 2 class khác cùng một lúc. Tuy nhiên với trait thì không có giới hạn. Ở trường hợp cuối khi compile sẽ ra lỗi.
class God needs to be a trait to be mixed in
Không thể tạo instance trực tiếp.
Nếu cố gắng tạo instance thì sẽ gặp lỗi như sau:
trait Saiyan
object {
val bardock = new Saiyan
// Compile error
// trait Saiyan is abstract; cannot be instantiated
}
Khi gặp trường hợp thế này thì bạn có 2 lựa chọn:
- Tạo một class kế thừa trait nói trên và tạo instance của class đó
- Tạo instance thông qua việc thêm implement
trait Saiyan
class SaiyanSoldier extends Saiyan
object SaiyanArmy {
val bardock = new SaiyanSoldier // OK
val kingVegeta = new Saiyan {} // OK
}
Không thể nhận constructor như class
Vì không thể tạo instance nên Trait cũng không có khả năng nhận constructor parameter như class.
trait Saiyan(name: String) {}
// Compile error
// traits or objects may not have parameters
Để giải quyết tình huống này cũng có 2 cách tương tự như trên:
- Tạo class kế thừa và gọi new với class đó
- Truyền thẳng paramter vào implement của trait
trait Saiyan {
val name: String
}
class SaiyanSoldier(val name: String) extends Saiyan
object SaiyanArmy {
val bardock = new SaiyanSoldier("bardock")
val kingVegeta = new Saiyan { val name = "kingVegeta" }
}
Diamond Problem
Diamond Problem là một danh từ khá nổi tiếng chỉ về một vấn đề trong cấu trúc kế thừa của lập trình hướng đối tượng. Với lý thuyết về Trait bên trên, bạn thử tưởng tượng nếu 2 trait cùng kế thừa một trait khởi điểm và override cùng một method, vậy khi class mới kế thừa 2 trait nói trên thì chuyện gì sẽ xảy ra?
trait A {
def greet(): Unit
}
trait B extends A {
def greet(): Unit = println("Hello!")
}
trait C extends A {
def greet(): Unit = println("Hi!")
}
class D extends B with C
// Copile error
/**
error: class D inherits conflicting members:
method greet in trait B of type ()Unit and
method greet in trait C of type ()Unit
(Note: this can be resolved by declaring an override in class D.)
class D extends B with C
*/
Cách giải quyết y như thông báo lỗi đã gợi ý, chúng ta sẽ override method trong class D. Khi overide bạn có thể gọi lại method từ trait nào đã kế thừa tùy thích hoặc thậm chí gọi cả hai luôn cũng được.
class D extends B with C {
override def greet(): Unit = {
super[B].greet()
super[C].greet()
}
}
Stackable Trait
Giờ mình sẽ nhắc đến một trường hợp đặc biệt hơn. Nếu định nghĩa hàm greet trong cả trait B và C đều có từ khóa override thì thứ tự kế thừa sau cùng trong câu khai báo class sẽ quyết định hàm nào được sử dụng. Điều này gọi là Linearization. Các trait ở trường hợp này gọi là Stackable Trait.
trait A {
def greet(): Unit = print(" Borned! ")
}
trait B extends A {
super.greet()
override def greet(): Unit = println("Hello!")
}
trait C extends A {
super.greet()
override def greet(): Unit = println("Hi!")
}
class D extends B with C
class E extends C with B
(new D).greet()
// Borned!
// Hello
// Hi
(new E).greet()
// Borned!
// Hi
// Hello
def greet(): Unit
}
trait B extends A {
abstract override def greet(): Unit = {
super.greet()
override def greet(): Unit = println("Hello!")
}
}
Và (lại dĩ nhiên rằng) với kiểu khai báo trên thì bạn không thể tạo ra một class kế thừa trực tiếp trait B được. Lý do là, method greet đã được định nghĩa đâu? Muốn thoát khỏi tình huống này, cần phải implement method greet thông qua một trait khác và kế thừa trait đó.
trait Saiyan {
def kamehameha(): Unit
}
trait SuperSaiyan {
self: Saiyan =>
def damage(): Unit = kamehameha()
}
trait SaiyanSoldier extends Saiyan {
def kamehameha(): Unit = println("KaaaaMeeeeHaaaMeeeeHaaaa!")
}
val vegeta = new SuperSaiyan with SaiyanSoldier
vegeta.damage()
Self types
Khi định nghĩa SuperSaiyan với từ khóa self: Saiyan như trên, trait SuperSaiyan có thể sử dụng method kamehameha trong khi bản thân không extends trực tiếp Saiyan. Khi tạo một trait SaiyanSoldier để implement thì có thể tạo được instance của SuperSaiyan.
Tạo sao lại phải tạo trait để implement thì lý do khá giống với phần abstract override bên trên, Scala không cho phép tạo instance với trait có method chưa được implement.
Vậy self types khác với extends trực tiếp thế nào? SuperSaiyanGod dưới đấy có hành vi gì khác với SuperSaiyan?
trait Saiyan {
def kamehameha(): Unit
}
trait SuperSaiyanGod extends Saiyan {
def damage(): Unit = kamehameha()
}
Câu trả lời là khi khởi tạo instance và chỉ rõ kiểu SuperSaiyan hay SuperSaiyanGod thì cách nhìn của instance đó với method gốc kamehameha() sẽ khác nhau!
val vegeta: SuperSaiyan = new SuperSaiyan with SaiyanSoldier
vegeta.kamehameha() // error: value kamehameha is not a member of SuperSaiyan
val goku: SuperSaiyanGod = new SuperSaiyanGod with SaiyanSoldier
goku.kamehameha() // KaaaaMeeeeHaaaMeeeeHaaaa
Bạn hãy chú ý đoạn val vegeta: SuperSaiyan =... và val goku: SuperSaiyanGod =... nhé!
Lời cuối
Lập trình hướng đối tượng trong Scala là không dễ. Và để áp dụng được thuần thục cũng... không dễ. Để hiểu hết thì phải làm thử việc refactoring cho một đoạn code chưa theo chuẩn hướng đối tượng, mà việc đó thì hơi quá sức với mình tại thời điểm này. Khi nào mình có thời gian sẽ nghiên cứu và viết tiếp.
>> Tìm hiểu thêm: Khủng hoảng đa lõi: Scala vs Erlang
Bizfly Cloud là nhà cung cấp dịch vụ điện toán đám mây với chi phí thấp, được vận hành bởi VCCorp.
Bizfly Cloud là một trong 4 doanh nghiệp nòng cốt trong "Chiến dịch thúc đẩy chuyển đổi số bằng công nghệ điện toán đám mây Việt Nam" của Bộ TT&TT; đáp ứng đầy đủ toàn bộ tiêu chí, chỉ tiêu kỹ thuật của nền tảng điện toán đám mây phục vụ Chính phủ điện tử/chính quyền điện tử.
Độc giả quan tâm đến các giải pháp của Bizfly Cloud có thể truy cập tại đây.
DÙNG THỬ MIỄN PHÍ và NHẬN ƯU ĐÃI 3 THÁNG tại: Manage.bizflycloud