cv.cm

Lập trình · Ngôn ngữ lập trình

Học Scala cơ bản cho người mới bắt đầu

8 phút đọc

Trả lời nhanh

Scala là ngôn ngữ kiểu tĩnh trên JVM, kết hợp lập trình hướng đối tượng và lập trình hàm, được dùng rộng rãi trong kỹ thuật dữ liệu với Apache Spark và cho các backend đồng thời. Các điểm mạnh là suy luận kiểu, collection bất biến, case class và pattern matching. Trang này dùng Scala 3 để trình bày cài đặt, giá trị, cấu trúc điều khiển, hàm, collection, trait, enum và xử lý lỗi bằng Option, Either và Try.

Scala là gì và dùng để làm gì

Scala là ngôn ngữ kiểu tĩnh kết hợp lập trình hướng đối tượng và lập trình hàm. Ngôn ngữ do Martin Odersky tạo ra tại EPFL, chạy chủ yếu trên Java Virtual Machine, kèm Scala.js cho trình duyệt và Scala Native cho tệp thực thi native. Mã Scala có thể dùng mọi thư viện Java.

Scala được dùng rộng rãi trong kỹ thuật dữ liệu (Apache Spark viết bằng Scala), cho các backend đồng thời và phân tán (Akka, Pekko, ZIO, Cats Effect), cũng như các hệ thống tài chính và xử lý luồng. Hướng dẫn này dùng cú pháp Scala 3, cho phép khối lệnh theo thụt lề; dấu ngoặc nhọn vẫn dùng được.

Cài đặt Scala và chạy Hello, world

Trình cài đặt được khuyến nghị là Coursier (cs setup), cài lệnh scala và một JDK. Trong Scala 3, phương thức có chú thích @main là điểm vào của chương trình.

// hello.scala
@main def hello(): Unit =
  println("Hello, world!")
scala run hello.scala
# Hello, world!

Với dự án lớn hơn, sbt và Mill là các công cụ build phổ biến. Scastie (scastie.scala-lang.org) chạy Scala ngay trên trình duyệt mà không cần cài gì.

Giá trị, biến và các kiểu cơ bản

Dùng val cho giá trị bất biến và var cho biến có thể thay đổi. Kiểu được suy luận tự động nhưng có thể ghi sau dấu hai chấm. Scala theo đúng phong cách dùng val gần như ở mọi nơi.

val name = "Scala"            // String
var count: Int = 1            // mutable
count += 1
val ratio = 3.0 / 4           // Double: 0.75
val big = 12345678901L        // Long
val ok = count > 1            // Boolean
println(s"$name: count=$count ratio=$ratio ok=$ok")
// Scala: count=2 ratio=0.75 ok=true
println(f"pi is ${math.Pi}%.3f")   // pi is 3.142
KiểuVí dụGhi chú
Int42số nguyên có dấu 32 bit
Long42Lsố nguyên có dấu 64 bit
Double3.14số thực dấu phẩy động 64 bit
Booleantruetrue or false
String"hi"chuỗi Java; hỗ trợ bộ nội suy s"..." và f"..."
Unit()giống void: không có giá trị có ý nghĩa

Mọi thứ trong Scala đều là biểu thức: if, khối lệnh và match đều trả về giá trị, nên bạn hiếm khi cần var tạm.

Cấu trúc điều khiển và pattern matching

if trả về giá trị. for lặp trên khoảng và collection, và khi kèm yield nó tạo ra collection mới. match là pattern matching mạnh mẽ của Scala, một tính năng đặc trưng của ngôn ngữ.

val n = 7
val parity = if n % 2 == 0 then "even" else "odd"
println(parity)                        // odd
for i <- 1 to 3 do print(s"$i ")       // 1 2 3
println()
val squares = for i <- 1 to 4 yield i * i
println(squares)                       // Vector(1, 4, 9, 16)
def describe(x: Any): String = x match
  case 0             => "zero"
  case i: Int if i < 0 => "negative int"
  case i: Int        => s"int $i"
  case s: String     => s"string of length ${s.length}"
  case (a, b)        => s"pair $a and $b"
  case _             => "something else"
println(describe(-5))                  // negative int
println(describe("hey"))               // string of length 3
println(describe((1, "x")))            // pair 1 and x

1 to 3 bao gồm 3; 1 until 3 dừng trước 3. case _ ở cuối là ký tự đại diện khớp mọi giá trị; nếu thiếu, giá trị không khớp sẽ ném MatchError.

Hàm và hàm bậc cao

Phương thức được định nghĩa bằng def. Hàm là giá trị: bạn có thể lưu, truyền làm đối số và trả về chúng. Tham số có thể có giá trị mặc định, và nơi gọi có thể dùng đối số có tên.

def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
  s"$greeting, $name!"
val double = (x: Int) => x * 2        // function value
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(add(2, 3))                    // 5
println(greet("Ann"))                 // Hello, Ann!
println(greet("Ann", greeting = "Hi")) // Hi, Ann!
println(applyTwice(double, 5))        // 20
println(List(1, 2, 3, 4).filter(_ % 2 == 0).map(_ * 10)) // List(20, 40)

Dấu gạch dưới trong _ * 10 là cách viết tắt cho lambda một tham số, x => x * 10.

Collection

Các collection chuẩn mặc định là bất biến: những thao tác như map, filter và :+ trả về collection mới và giữ nguyên bản gốc. Phiên bản có thể thay đổi nằm trong scala.collection.mutable.

val nums = List(5, 3, 8)
val more = nums :+ 1                  // List(5, 3, 8, 1); nums unchanged
println(more.sorted)                  // List(1, 3, 5, 8)
println(nums.sum)                     // 16
val v = Vector("a", "b", "c")         // fast indexed access
println(v(1))                         // b
val ages = Map("Ann" -> 31, "Bo" -> 25)
println(ages("Ann"))                  // 31
println(ages.get("Zed"))              // None
println(ages.getOrElse("Zed", 0))     // 0
val unique = Set(1, 2, 2, 3)
println(unique.size)                  // 3
val words = List("apple", "avocado", "banana")
println(words.groupBy(_.head)('a'))  // List(apple, avocado)
CollectionDùng cho
Listdanh sách liên kết bất biến; thêm vào đầu và truy cập head/tail nhanh
Vectordãy bất biến có chỉ mục; lựa chọn mặc định tốt cho đa số trường hợp
Maptra cứu theo khóa-giá trị
Setphần tử duy nhất và kiểm tra thành viên
Arraymảng Java kích thước cố định, có thể thay đổi

Case class, trait và enum

case class là kiểu dữ liệu bất biến tự động có so sánh bằng, toString, copy và hỗ trợ pattern matching. trait giống interface nhưng có thể chứa cả mã. Scala 3 bổ sung enum cho kiểu dữ liệu đại số.

trait Shape:
  def area: Double
case class Circle(r: Double) extends Shape:
  def area: Double = math.Pi * r * r
case class Rect(w: Double, h: Double) extends Shape:
  def area: Double = w * h
enum Color:
  case Red, Green, Blue
@main def shapes(): Unit =
  val all: List[Shape] = List(Circle(1), Rect(2, 3))
  all.foreach(s => println(f"$s area=${s.area}%.2f"))
  // Circle(1.0) area=3.14
  // Rect(2.0,3.0) area=6.00
  val r = Rect(2, 3)
  println(r == Rect(2, 3))            // true
  println(r.copy(h = 10))             // Rect(2.0,10.0)
  println(Color.values.toList)        // List(Red, Green, Blue)

Các tính năng đặc trưng khác gồm given và using (tham số theo ngữ cảnh, trước đây là implicit), extension method, opaque type và union type như Int | String.

Xử lý lỗi với Option, Either và Try

Scala hỗ trợ try, catch và finally, nhưng mã theo phong cách đặc trưng mô hình hóa lỗi bằng kiểu. Option là Some(value) hoặc None; Either chứa lỗi Left hoặc kết quả Right; Try bắt ngoại lệ thành Failure.

import scala.util.{Try, Success, Failure}
def parseInt(s: String): Option[Int] = s.toIntOption
def safeDivide(a: Int, b: Int): Either[String, Int] =
  if b == 0 then Left("division by zero") else Right(a / b)
println(parseInt("42"))               // Some(42)
println(parseInt("x"))                // None
println(safeDivide(10, 2))            // Right(5)
println(safeDivide(1, 0))             // Left(division by zero)
Try("abc".toInt) match
  case Success(n) => println(s"got $n")
  case Failure(e) => println(s"failed: ${e.getClass.getSimpleName}")
// failed: NumberFormatException

Một chương trình hoàn chỉnh nhỏ: bảng điểm

Chương trình này phân tích các dòng name,score, bỏ qua dòng không hợp lệ và in điểm trung bình của từng học sinh. Nó dùng case class, Option, groupBy và định dạng chuỗi.

case class Score(name: String, value: Int)
def parse(line: String): Option[Score] =
  line.split(",").map(_.trim) match
    case Array(name, v) => v.toIntOption.map(Score(name, _))
    case _              => None
@main def report(): Unit =
  val lines = List("ann, 90", "bo, 70", "ann, 80", "broken line", "bo, x")
  val scores = lines.flatMap(parse)
  val averages = scores.groupBy(_.name).view.mapValues { list =>
    list.map(_.value).sum.toDouble / list.size
  }.toList.sortBy(_._1)
  for (name, avg) <- averages do
    println(f"$name%-4s $avg%.1f")
// ann  85.0
// bo   70.0

Học tiếp ở đâu

  • Scala 3 Book tại docs.scala-lang.org, bao quát ngôn ngữ từ cơ bản đến các trừu tượng theo ngữ cảnh.
  • Scala Toolkit cho các tác vụ thường ngày như tệp, JSON và HTTP.
  • Tài liệu Apache Spark nếu bạn muốn dùng Scala để xử lý dữ liệu.
  • Các khóa học được liên kết từ scala-lang.org, gồm các khóa lập trình hàm của EPFL.

Câu hỏi thường gặp

Nên học Scala 2 hay Scala 3?

Hãy học Scala 3 cho dự án mới. Scala 2.13 vẫn còn trong nhiều codebase hiện có, kể cả một phần hệ sinh thái Spark, và phần lớn khái niệm cốt lõi là như nhau.

Scala có phải ngôn ngữ lập trình hàm không?

Scala vừa hướng đối tượng vừa hàm. Mọi giá trị đều là đối tượng, nhưng hàm là giá trị hạng nhất, collection mặc định bất biến và pattern matching đóng vai trò trung tâm.

val và var trong Scala khác nhau thế nào?

val định nghĩa giá trị không thể gán lại, còn var định nghĩa biến có thể gán lại. Scala theo phong cách ưu tiên val cùng collection bất biến.

Case class là gì?

Case class là lớp dành cho dữ liệu bất biến. Trình biên dịch tự sinh so sánh bằng theo cấu trúc, toString dễ đọc, phương thức copy và hỗ trợ pattern matching.

Scala còn được dùng không?

Có. Scala vẫn là ngôn ngữ cốt lõi của Apache Spark, của các nền tảng dữ liệu và xử lý luồng, và của các thư viện backend hàm như ZIO và Cats Effect.

Tất cả hướng dẫn lập trình