Scala là một ngôn ngữ dị nhất mà tôi từng làm việc. Để học nó tôi phải mất nhiều thời gian hơn C++ (phần generic programming khá khó), hay C# (dễ), Java (quá thân quen) hay Objective-C (khá giống với C). Sau khi đã quen với Scala thì tôi nghiệm ra Scala khó dùng với các lập trình vì chính những ưu điểm mạnh của nó mà các ngôn ngữ khác không có, hoặc có nhưng chứa đầy đủ, mềm dẻo:
- Lập trình hàm ~ Functional Programming
- Hướng đối tượng rất mềm dẻo (trait)
- Cú pháp không dùng dot notation (object.method ~ đối-tượng.phương-thức)
- Xu hướng đặt tên biến ngắn gọn
- Không dùng ; để kết thúc một lệnh như C++, C, Java, C#.
- Vừa là ngôn ngữ biên dịch vừa là ngôn ngữ REPL (gõ trực tiếp, xử lý từng dòng lệnh)
- Số lượng API có sẵn rất lớn, chưa kể nhúng được cả ngôn ngữ Java. Nhiều quá đôi khi làm lập trình viên mới học hoảng.
Trong bài viết này, tôi lấy ví dụ về lập trình hàm (functional programming) trong Scala. Bạn nào đã lập trình iOS, chắc biết block, với C# là Lambda function. Lập trình hàm Scala cung cấp mấy võ sau cho lập trình viên: xử lý, thao tác trên các phần tử của tập rất gọn gàng, nối chuỗi các hàm lại, lập trình không đồng bộ, đa nhiệm…
– Procedure ~ Thủ tục: nhóm các lệnh lại để dễ tái sử dụng. Procedure thường không thuộc một Class hay Object nào cả.
– Method ~ Phương thức: tương tự như Procedure, nhưng nó thuộc Class hoặc Object. Ở một số ngôn ngữ sẽ có giới hạn truy xuất với method: public, protected, private và cả overwrite (lớp con thừa kế viết đè lên) và overload (cùng tên hàm, kiểu tham số khác nhau)
– Function ~ Hàm: giống thủ tục vì nó cũng là nhóm các lệnh, nhưng nó có thể hoạt động như biến, hoặc kiểu. Function có thể bị gán qua lại giữa các đối tượng, hoặc thậm chí hàm khác nhau. Hàm có thể được khai báo trong hàm, trong phương thức hoặc khai báo vô danh (anonymous function).
Rõ ràng Function cơ động hơn Procedure (không gán được như biến) và Method (luôn ràng buộc vào Class hay Object). Rất khó để nối chuỗi các Method (method chaining) nhưng với Function lại đơn giản, kết quả của hàm A lại là đầu ra của hàm B…..
A => B hay A => Function
Ví dụ Function Programming trong Scala:
//Định nghĩa hàm
scala> val x2 = (i : Int) => { i * 2} //
x2: Int => Int = <function1>
//Gọi hàm
scala> x2(10)
res2: Int = 20
//Nối hàm
scala> x2(5).toString
res3: String = 10
//Định nghĩa hàm nhận vào 1 tham số String, trả về String khác đã upper cae
scala> val uString = (txt: String) => {txt.toUpperCase}
uString: String => String = <function1>
//Thử với một chuỗi đơn giản
scala> uString("TechMaster")
res4: String = TECHMASTER
//Thao tác từng phần tử trong một List
val list = List("One", "Two", "Three", "Four", "Five")
list.map(uString)
res5: List[String] = List(ONE, TWO, THREE, FOUR, FIVE)
Khai báo hàm với kiểu trả về tự suy và tường minh
val f = (i: Int) => { i % 2 ==0 } //tham số vào là i: Int, kiểu trả về tự hiểu ngầm là Boolean
val f: (Int) => Boolean = i => {i % 2 ==0} //Kiểu trả về tường minh là Boolean
val f: Int => Boolean = i => {i % 2 ==0} //Bỏ ngoặc () quanh tham số vào
val f: Int => Boolean = i => i % 2 ==0 //Bỏ ngoặc {} nếu thân hàm chỉ có 1 lệnh
val f: Int => Boolean = i => {_ % 2 ==0} //Thay i bằng biến tượng trưng _
Đây chỉ một hàm kiểm tra số chia hết cho 2 mà đã có 5 cách viết, với cú pháp chả giống ai. Vậy cứ từ từ xem các bài tiếp theo, chắc chắn bạn sẽ hiểu Scala.
Thêm ví dụ nữa
//Đầu vào 2 tham số String, nối lại rồi chuyển sang Integer
scala> val f = (txt1: String, txt2: String) => {
| val txt = txt1.concat(txt2)
| txt.toInt}
f: (String, String) => Int = <function2>
scala> f("1", "0")
res8: Int = 10
Ví dụ về đặc tính viết ít hiểu nhiểu của Scala so với Java
Ví dụ hàm dưới lọc các phần tử chia hết cho 2 trong mảng, rồi chia tiếp cho 2 với từng phần tử.
Java
List<Integer> iList = Arrays.asList(2, 7, 9, 8, 10);
List<Integer> iDoubled = new ArrayList<Integer>();
for(Integer number: iList){
if(number % 2 == 0){
iDoubled.add(number / 2);
}
}
Scala chỉ 2 dòng
val iList = List(2, 7, 9, 8, 10);
val iDoubled = iList.filter(_ % 2 == 0).map(_ / 2)
Bình luận