[Scala] - day 8 : Collections trong Scala [phần cuối] - Monadic Collections

Phần cuối cùng về collections chúng ta sẽ tìm hiểu về monadic, từ “monadic” trong tiếng Hi Lạp gốc có nghĩa là single unit.
Ok,chúng ta sẽ bắt đầu với Option, 1 kiểu monadic collection được extend từ Iterable.

Option Collections

Là 1 collection chỉ có không quá 1 element,Option chứa đựng 1 giá trị đơn hoặc 1 giá trị rỗng.
Option thường được lựa chọn để thể hiện những giá trị không bắt buộc ,có thể null có thể có.
Kiểu Option có 2 subtype :
  • Some, có 1 giá trị.
  • None, được coi là 1 empty collection. Kiểu None không có parameter type bởi nó không bao giờ có giá trị. 
Bạn có thể gọi trực tiếp 2 kiểu trên hoặc sử dụng nó qua việc khởi gọi Option() để tự bắt trường hợp null và tự xác định kiểu của Option. 
Ta thử vài ví dụ :
Sử dụng trực tiếp None và Some
scala> val dayLaNone: Option[String] = None
dayLaNone: Option[String] = None 
scala> val dayLaSome: Option[String] = Some("HoaiPT")
dayLaSome: Option[String] = Some(HoaiPT)
Sử dụng qua việc khởi gọi Option
scala> val noneVoiOption: Option[String] = Option(null)
noneVoiOption: Option[String] = None 
scala> val someVoiOption: Option[String] = Option("HoaiPT")
someVoiOption: Option[String] = Some(HoaiPT)
Để lấy được element từ Option hay đúng hơn từ Some bạn phải "get" nó ra,tuy nhiên với Option kết quả có khi là None mà None.get sẽ trả về lỗi vì bạn đòi "get" cái gì đó từ chỗ "không có gì", vậy với Option tôi khuyên bạn nên kiểm tra trước khi muốn lấy giá trị từ nó.Có 2 cách đơn giản để an toàn hơn với Option đó là sử dụng :
  • isDefined và isEmpty : kiểm tra Option trả về None hay không.
  • getOrElse(giá_trị_default) : get element từ Option nếu None thì trả về gía trị default.
scala> Option(null).isDefined
res1: Boolean = false 
scala> Option(null).isEmpty
res2: Boolean = true 
scala> Option(null).getOrElse("HoaiPT")
res3: String = HoaiPT
day 5 - bắt đầu với List các bạn đã biết để truy xuất element đầu tiên từ List ta có thể sử dụng head , tuy nhiên nếu list rỗng thì method head sẽ trả về lỗi, để an toàn hơn các bạn có thể sử dụng headOption , và kết quả sẽ là 1 Option.
scala> List().headOption
res4: Option[Nothing] = None 
scala> List(1,2,3,4).headOption
res5: Option[Int] = Some(1)
***) Notes :
  • method find trả về option
  • match 1 option trả về 2 case : None và Some(thing) 

Try Collections

Kiểu util.Try collection trả về error handling trong 1 collection. Nó cung cấp 1 cơ chế để bắt tất cả error xảy ra trong quá trình thực thi, và kết quả của nó là  error hoặc kết quả của function nếu thành công.
Scala cung cấp cho chúng ta khả năng kiểm soát error bằng cách throw exceptions, là 1 kiểu error mà chứa message hay thông tin nào đó.Khi bạn throw 1 exception trong Scala nó sẽ phá vỡ follow của chương trình và trả về cho control gần nhất exception đó. Do đó Exceptions sẽ chấm dứt chương trình của bạn, hầu hết các framework Scala đều cố gắng ngăn chặn điều này.
Exception có thể được throw bằng code của bạn , bằng thư viện mà bạn sử dụng hay bằng Java Virtual Machine (JVM). JVM sẽ throw java.util.NoSuchElementException nếu bạn gọi None.get hoặc Nil.head (lấy head của list rỗng) hoặc java.lang.NullPointerException nếu bạn access tới những trường hoặc method của 1 giá trị null.
Ta sử dụng cấu trúc sau để trả về 1 exception : throw new Exception , message trong Exception là optional :
scala> throw new Exception("Pham Thanh Hoai")
java.lang.Exception: Pham Thanh Hoai
   ... 32 elided
Ta thử với ví dụ cụ thể :
scala> def phepChia(soBiChia : Int, soChia : Int) = soBiChia / soChia
phepChia: (soBiChia: Int, soChia: Int)Int 
scala> phepChia(9,3)
res1: Int = 3 
scala> phepChia(9,0)
java.lang.ArithmeticException: / by zero
   at .phepChia(<console>:11)
   ... 32 elided 
scala> util.Try(phepChia(9,0))
res3: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero) 
scala> util.Try(phepChia(9,3))
res4: scala.util.Try[Int] = Success(3)
Tôi viết lại function phepChia trả về kiểu collection Try:
scala> def phepChia(soBiChia : Int, soChia : Int) = util.Try{soBiChia / soChia}
phepChia: (soBiChia: Int, soChia: Int)scala.util.Try[Int]
***) Notes :
  • match 1 try collection trả về 2 case là : util.Success(x) và util.Failure(error)

Future Collections

Monadic collection cuối cùng đó là concurrent.Future, dành cho back‐ ground task. Không như Option và Try, future có thể không được available ngay khi khởi tạo, bởi vì back‐ ground task tạo ra future đó có thể vẫn còn đang được thực thi.
Như bạn đã biết Scala chạy trên nền Java Virtual Machine, và nó cũng có thể được chạy ở trong những Java’s “threads,” trong các tiến trình của JVM. Mặc đinh Scala chạy trong các JVM’s “main” thread, nhưng nó cũng hỗ trợ để chạy đồng thời như background task. Khởi tạo 1 future bằng 1 function sẽ execute function đó trong 1 thread riêng biệt trong khi thread hiện tại vẫn tiếp tục chạy. 
Nghe thì hơi khó hiểu nhưng đơn giản là để tạo 1 future bạn chỉ cần gọi đến function mà bạn muốn chạy ngầm.
Ok, hãy tạo vài ví dụ để rõ ràng hơn, để tạo 1 future điều cần thiết là chỉ rõ "context" trong session hiện tại hay application để chạy 1 function ngầm, do đó tôi cần import thêm library.
scala> import concurrent.ExecutionContext.Implicits.global
import concurrent.ExecutionContext.Implicits.global
scala> val future = concurrent.Future { println("hi") }
hi
future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@1de5cc88
Bạn thấy background task in ra "hi" trước khi trả về giá trị cuối cùng.Tôi sẽ thêm sleep vào background.
scala> val future = concurrent.Future {Thread.sleep(5000); println("hi") }
future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@14874a5d 
scala> println("đang chạy")
đang chạy 
scala> hi
Tôi để background ngủ 5s và trong lúc đợi tôi in ra "đang chạy" ,và sau 5s tôi mới nhận được kết quả của println("hi").
Futures có thể được quản lý không đồng bộ (trong khi các "main" thread tiếp tục hoạt động) hay đồng bộ (với "main" thread chờ đợi cho các nhiệm vụ hoàn thành).
Handling futures asynchronously - không đồng bộ.
Với Futures, trong quá trình sinh ra các background task, có thể treat để trả về 1 kiểu monadic collection khác. Bạn có thể xâu chuỗi hay móc nối các function trong future lại, kết quả của function trước là input cho function sau.
Một future được handle như vậy sẽ trả về kiểu util.Try - trả về value hoặc exception. Với trường hợp success (trả về value),chuỗi function hay future sẽ passed (chạy tuần tự và không có exception) và trả về value sau đó được convert thành future để chủ động trả về trả về success hay failure.Với trường hợp failure (1 function trong chuỗi function trả về exception) function tiếp sau nó sẽ không được thực thi.Bằng cách này future chỉ là một chuỗi các function mang giá trị được embed.Điều này tương tự với Try ,chuỗi function bị phá vỡ khi có lỗi (exception), hay Option , chuỗi function bị phá vỡ và không trả về value.
Để nhận được kết quả cuối cùng của future hay 1 chuỗi các future bạn cần chỉ rõ call back function.Call back function của bạn sẽ nhận được giá trị success cuối cùng hay 1 exception ,giải phóng code và chạy function tiếp.
Ta tạo 1 phương trình Future đơn giản gồm 2 chức năng ,define function sinh ra 1 số ngẫu nhiên và kiểm tra số đó với 0.
scala> def simpleFuture(i : Int) = Future{
         | def rand(x: Int) = util.Random.nextInt(x)
         |
         | Thread.sleep(rand(5000))
         | if (rand(i) > 0) i else throw new Exception
         | }
simpleFuture: (i: Int)scala.concurrent.Future[Int]
 Có 3 method đi kèm với Future là :
  • onComplete : sau khi task trong Future hoàn thành,kết quả được sử dụng như là
    util.Try chứa value (nếu success) hoặc exception (nếu failure).
  • onFailure : nếu task trong Future trả về exception nó sẽ bắt exception đó.
  • onSuccess : sau khi task trong Future chạy thành công (không có exception) nó sẽ bắt value cuối cùng mà Future trả về.
scala> simpleFuture(1) onComplete {println(_)}
scala> Failure(java.lang.Exception) 
scala> simpleFuture(1) onFailure { case _ => println("Error!") }
scala> Error! 
Function chúng ta có trả về Failure nên tôi viết lại 1 Future đơn giản cho ví dụ về onSuccess.
scala> Future{87} onSuccess { case x => println(s"Got $x") }
Got 87
Ở ví dụ về onSuccess kết quả trả về ngay còn onComplete và onFailure kết quả mất 1 thời gian mới trả lại, bạn thử xem sự khác nhau giữa simpleFuture và Future mới.
Handling futures synchronously - có đồng bộ
Ở phần trên như bạn thấy khi ta gọi 1 Future nó ngay lập tức trả về 1 scala.concurrent.impl.Promise sau đó các task trong Future đó chạy bất đồng bộ.Có 1 cách để bạn bắt Future chạy đồng bộ với main thread, nghĩa là khi bạn khởi chạy 1 Future nó phải chạy đến kết quả cuối cùng cho bạn chứ không chỉ đưa cho bạn 1 lời hứa (scala.concurrent.impl.Promise). Đó là sử dụng concurrent.Await.result() .
scala> import concurrent.duration._
import concurrent.duration._ 
scala> val maxTime = Duration(10, SECONDS)
maxTime: scala.concurrent.duration.FiniteDuration = 10 seconds 
scala> concurrent.Await.result(simpleFuture(2), maxTime)
java.lang.Exception
  at $anonfun$simpleFuture$1.apply$mcI$sp(<console>:19)
  at $anonfun$simpleFuture$1.apply(<console>:15)
  at $anonfun$simpleFuture$1.apply(<console>:15)
  at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
  at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
  at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:121)
  at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
  at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
  at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
  at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107) 
scala> concurrent.Await.result(simpleFuture(5), maxTime)
res66: Int = 5
Bạn sẽ thấy 1 giá trị mới mà tôi vừa định nghĩa đó là maxTime, đúng với tên của nó, đó chính là thời gian bạn cho phép Future được thực thi, nếu quá thời gian đó mà Future vẫn chưa có thể đưa cho bạn kết quả cuối cùng ta sẽ nhận được lỗi java.util.concur rent.TimeoutException.

OK, đến đây là toàn bộ chia sẻ của tôi về phần 1 Scala - Functional Programming. Phần tiếp theo chúng ta sẽ cùng tìm hiểu về Scala - Object-Oriented.
Monadic Collections

Comments

  1. Cảm ơn bạn rất nhiều. Bài viết rất hữu ích

    ReplyDelete

Post a Comment