Sự khác nhau giữa Scala và Java 8 (Phần 1)
1. Scala - ngôn ngữ lập trình hàm (Functional Language) "write less, do more" Ví dụ , nhu cầu ứng dụng của bạn cần lựa chọn một số từ (string) thích hợp thỏa mãn tiêu chí điều kiện của bạn từ một danh sách các từ - List(). Để thực hiện logic này trong Java (7, 6), cú pháp sẽ có ...
1. Scala - ngôn ngữ lập trình hàm (Functional Language)
"write less, do more"
Ví dụ, nhu cầu ứng dụng của bạn cần lựa chọn một số từ (string) thích hợp thỏa mãn tiêu chí điều kiện của bạn từ một danh sách các từ - List(). Để thực hiện logic này trong Java (7, 6), cú pháp sẽ có dạng:
List<String> filteredList = LinkeList<String>(); // giả sử chúng ta có danh sách các từ trong không gian mẫu là stringToFilterList for(stringToFilter : stringToFilterList){ if(StringUtils.isNotBlank(stringToFilter) && stringToFilter.startsWith("A")){ filteredList.add(stringToFilter); } }
Mình vừa thực hiện 3 bước để lấy kết quả mong muốn. Cùng trả lại kết quả như vậy nhưng trong Scala, các bước sẽ ngắn gọn hơn với lambda expressions:
// giả sử chúng ta có danh sách các từ trong không gian mẫu là listStringToFilter val filteredList = stringToFilterList.filter(s => s.nonEmplty()n && s.startsWith("A"))
Trong ví dụ trên, cách mà Java phát biểu là : "cần tạo ra một không gian lưu trữ dữ liệu lọc, lọc từng từ trong không gian mẫu và nếu từ nào thỏa mãn điều kiện bắt đầu bằng chữ A thì sẽ lưu lại". Còn Scala phát biểu rằng : "lọc tất cả các từ mà từ đó thỏa mãn điều kiện bắt đầu bằng chữ A thì sẽ lưu lại".
Theo ý kiến cá nhân thì không thể phủ nhận rằng Java là ngôn ngữ giải thích nghĩa đen một vấn đề rất trong sáng. Nhưng hãy xem cách mà Scala diễn giải cũng khá dễ hiểu và có phần ngắn gọn hơn.
Trong Java 8 hỗ trỡ lambda, code có thể viết lại như sau :
List<String> filteredList = stringToFilterList.stream().filter(s => StringUtils.isNotBlank(s) && s.startsWith("A")).collect(Collectors.toList())
Dù việc viết các dòng code so với Java (7, 6) đã ít hơn, xong so với Scala nó dường như vẫn quá dài.
2. Monads
Mình tạm dịch là 'đơn nguyên' hay 'đơn tử'. Đây là một khái niệm để lập trình hàm từ toán học. Khái niệm này dường như khó hiểu, nhưng nó lại không khó để học, mời các bạn cùng xem tiếp phần dưới đây.
Chúng ta sẽ nói về lợi ích của monads trpng lập trình, và đối với newbie mới học sẽ gặp các monads phổ biến nhất trong Scala như:
2.1 "Maybe"M monad.
Maybe monad thường được gọi như là một "Optional types", nó implemented một Option[T]. Option[T] này xác định rõ 2 trạng thái: một là Some[T] (khi giá trị thực của T tồn tại), hai là None nếu Option rỗng.
Trong Scala chúng ta sẽ gặp cách khai báo :
val maybeString = Option(someOrtherString)
Trong Java bạn sẽ cần kiểm tra nếu(if) tồn tại thì gắn giá trị T không(else) thì gắn bằng None. Nhưng Option đã thay bạn làm việc kiểm tra này.
Scala cũng cung cấp một số phương pháp khác:
// matching of state, cách hoạt động gần giống như switch trong java maybeString match { case Some(s) => { // xử lý code với string được matching } case None => { // xử lý code khi None } } // lấy giá trị lưu trong monads, hoặc sử dụng giá trị mặc định nếu monad rỗng maybeString.getOrElse("defaultValueOfThisString") // lấy giá trị lưu trong monads, hoặc thực thi một function nếu monad rỗng maybeString.orElse({ // xử lý logic của bạn log.warn("Using default value!!!") Some("defaultValueOfThisString") }).get // và orElse sẽ không bao giờ nhận một giá trị None.get ở đây // Bạn muốn kiểm tra trước lấy giá trị của maybeString..... if (maybeString.isEmpty) { ... } // Bạn muốn kiểm tra với một số điều kiện khác if (maybeString.exists(_.startsWith("somePredicate"))) { ... }
2.2 Try
Cách thức hoạt động của Try trong Scala về ý nghĩa là giống Java.
Try({ //...code của bạn }) ... // có thể gọi các class trong Try Try(SomeClass.someMethod()) ...
Try cũng có hai trạng thái: Success, khi khối code của bạn không sinh ra ngoại lệ; Failure khi trong khối code trong Try có lỗi xảy ra. Chúng ta hãy cùng xem
// matching of state Try(SomeClass.someMethod()) match { case Success(s) => { // xử lý code với kết quả } case Failure(e) => { // xử lý code với exception } } // Khi muốn xử lý khi Try nếu Failure Try(SomeClass.someMethod()).recover({ // xử lý }) // Thêm một số điều kiện trước khi Try trả về kết quả Success Try(SomeClass.someMethod()).filter({ // điều kiện của bạn }) // Khi mà chỉ quan tâm tâm kết quả trả về Try(SomeClass.someMethod()).toOption // Lấy giá trị nếu khối Try Success hoặc giá trị mặc định Try(SomeClass.someMethod()).getOrElse("somethingDefault") // Nếu cần làm gì đó nhiều hơn việc chỉ đơn giản trả về kết quả Try(SomeClass.someMethod()).orElse({ log.error("We couldn't get that thing for you, enjoy your default value") Success("somethingDefault") }).get // Nếu chỉ muốn biết kết quả khối Try Try(SomeClass.someMethod()).isSuccess Try(SomeClass.someMethod()).isFailure
2.3 Future
Đây là một trong những điều rất thú vị, nó mở ra mộ không gian mới trong việc lập trình không đồng bộ - asynchronous programming. Đây cũng là một trong số rất nhiều lý do làm cho Scala trở nên là một ngôn ngữ mạnh. Ví dụ :
scala> val future = concurrent.Future{println("A"); println("B")} A B future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@1725dc0f
Việc thực thi từng lệnh code trong Future lần lượt, kết quả là backgound task in ra chữ A, sau đó là chữ B rồi mới đưa ra kết quả của Future. Nhưng hãy xem chuyện gì xảy ra khi mình thêm một Thread bên trong Future:
scala> val future = concurrent.Future{println("A");Thread.sleep(5000);println("B")} A future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@6989da5e scala> B
Khi đó backgound task in ra chữ A, trả về kết quả của Future và sau đó chờ 5s mới in ra chữ C. Đáng nhẽ phải in ra chữ C rồi mới in ra kết quả thực thi của Future. Điều này chứng tỏ sự không đồng bộ đã xảy ra, tức là main Thread tiếp tục hoạt động mà không chờ sub Thread trong Future thực hiện xong. (Bài này mang tính chất so sánh nên mình không đi vào chi tiếp, các bạn có thể tìm hiểu thêm về Future tại đây)
Như đã thấy Scala tuân thủ một tập quy tắc, dễ dàng chuyển đổi. Điều này cho phép chúng ta tập trung vào việc thực hiện logic của ứng dụng.
Để tránh bài viết quá dài gây khó theo dõi với người đọc, bài viết này mình xin dừng ở đây. Các bạn đón đọc phần 2 loạt bài của mình nhé.
Mình xin cảm ơn và rất mong nhận được sự góp ý của các bạn :)