Scala là gì? Thông tin về ngôn ngữ lập trình bậc cao Scala

1630
08-03-2018
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ập trình hướng đối tượng và lập trình hàm - Ảnh 1.

Scala kế thừa những tính năng hướng đối tượng từ Java

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 tương thích với Java

Code Scala khi biên dịch ra cũng sẽ cùng mã bytecode của JVM

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" }

}

Scala - Lập trình hướng đối tượng và lập trình hàm - Ảnh 9.

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

TAGS: scala
SHARE