Scala – For expression

Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu về khái niệm For Expression hoặc For Comprehesion trong Scala, mà trong ngôn ngữ khác chúng thường quen với khái niệm For Loop

Scala developer tương đối quen thuộc với For Comprehension nhưng hầu hết chúng ta sẽ tự hỏi For Comprehension này hoạt động như thế nào. Cơ bản thì nó sẽ chuyển đổi một biểu thức thành công cụ map, flatMap. Nhiều lập trình viên chia For Expression thành 2 dạng sau:

For Loop – vòng lặp quen thuộc

for ( i <- generator ) { statements } ​

For Comprehension

for ( i <- generator; a = definition; if (filter)) yield a

Thực ra cả hai trường hợp trên đều giống nhau và chỉ khác nhau về cách chúng ta gọi và sử dụng. Do đó, for trong scala là một expression bởi vì tất cả mọi hành vi của hàm đều là expression (biểu thức), còn lại là dựa trên cách chúng ta gọi và sử dụng.

For Expression có 3 đặc tính

p <- persons            // Một Generator
n = p.name             // Một Definition
if(n startsWith "Some") // Một Filter

Làm thế nào chúng ta sử dụng For Expression trên các class collection tuỳ chỉnh hoặc wrapper tuỳ chỉnh giống như Option?

Chúng ta sẽ có một ví dụ như sau

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer

case class CustomList[A](elements: A*) {      
  val elems = new ArrayBuffer[A]
  elems ++= elements
}

val list = CustomList(1, 2, 3)
for( i <- list) { println(i) }

Kết quả

<console>:14: error: value foreach is not a member of CustomList[Int]
       for( i <- list) { println(i) }

Chúng ta sẽ nhận được lỗi khi chạy như ở trên.  Nhưng lỗi bên trên tương đối dễ hiểu, chúng ta có thể hiểu rằng hàm foreach đang bị thiếu, nguyên nhân là vì trình biên dịch không thể chuyển đổi for expression sang thành phương thức map, flatMap, withFilter và foreach.

Theo lỗi này, chúng ta đang sử dụng một cách đơn giản như for loop nhưng nó hoạt động như for expression. Nếu bạn muốn hiểu rõ hơn thì có thể dùng lệnh sau scalac-Xprint:all file.scala để hiển thị thông tin khi trình biên dịch chuyển đổi for expression thành các phương thức

Nếu bạn muốn sử dụng for expression cho các class hoặc wrapper của mình thì nó phải hỗ trợ các method sau:

def foreach(c: A => Unit): Unit
def map[B](f: A => B): CustomList[B]
def withFilter(p: A => Boolean): CustomList[A]
def flatMap[B](f: A => CustomList[B]): CustomList[B]

Làm thế nào sử dụng một generator và in ra các giá trị?

Để thực hiện được việc này, chúng ta phải implement hàm foreach trong class collection

def foreach(c: A => Unit): Unit = {
  elems.foreach(c)
}

Làm thế nào để sử dụng definition và generator cùng nhau?

Chúng ta phải implement phương thức map. Nếu không, map của chúng ta không thể sử dụng phần definition trong For Expression

def map[B](f: A => B): CustomList[B] = {
  val temp = elems.map(f)
  CustomList(temp: _*)
}

Làm thế nào để sử dụng for/yield, chúng ta phải sử dụng phương thức nào?

Điều này dựa trên yêu cầu của bài toán. Nếu bạn chỉ sử dụng một generator thì sẽ chỉ có một phương thức map được yêu cầu, nhưng với nhiều generator, chúng ta phải thêm vào flatMap nếu không muốn bị lỗi.

Hãy xem ví dụ bên dưới đây với trường hợp sử dụng nhiều generator

def flatMap[B](f: A => CustomList[B]): CustomList[B] = {
  val temp = elems.map(f)
  flatternLike(temp)
}

def flatternLike[B](seq: Seq[CustomList[B]]): CustomList[B] = {
  val temp = new ArrayBuffer[B]
  for(i <- seq) {
    for(j <- i) {
      temp += j
    }
  }
  CustomList(temp: _*)
}

Thông thường trong collection chúng ta sử dụng phương thức filter nhưng tại sao ở đây chúng ta sử dụng withFilter?

Bởi vì filter theo một quy tắc rõ ràng nhưng withFilter hoạt động theo cách lazy. Khi chúng ta filter, nó luôn luôn trả về một collection mới nhưng withFilter sử dụng tính toán lazy và kiểm tra từng phần tử một.

Bất cứ khi nào chúng ta muốn tạo một collection và sử dụng nó với for expression, chúng ta có cần implement tất cả các phương thức không?

Chúng ta không cần phải làm thế, nó sẽ dựa theo yêu cầu của bài toán

  • Chỉ sử dụng giống for loop, không sử dụng yield – foreach
  • Cần sử dụng một generator với yield – map
  • Cần sử dụng nhiều generator mà không cần yield – foreach
  • Cần sử dụng nhiều generator với yield – foreach, map và flatmap
  • Cần sử dụng một generator với definition – map
  • Cần sử dụng generator với filter mà không cần yield – withFilter

Sau đây là toàn bộ phần code của chúng ta

/* Custom Collection */
import scala.collection.mutable.ArrayBuffer

case class CustomList[A](elements: A*) {
  val elems = new ArrayBuffer[A]
  elems ++= elements

  def foreach(c: A => Unit): Unit = {
    elems.foreach(c)
  }

  def map[B](f: A => B): CustomList[B] = {
    val temp = elems.map(f)
    CustomList(temp: _*)
  }

  def withFilter(p: A => Boolean): CustomList[A] = {
    val temp = elems.filter(p)
    CustomList(temp: _*)
  }

  def flatMap[B](f: A => CustomList[B]): CustomList[B] = {
    val temp = elems.map(f)
    flatternLike(temp)
  }

  def flatternLike[B](seq: Seq[CustomList[B]]): CustomList[B] = {
    val temp = new ArrayBuffer[B]
    for(i <- seq) {
      for(j <- i) {
        temp += j
      }
    }
    CustomList(temp: _*)
  }
}


object ForExpression extends App {

  val ints = CustomList(1, 2, 3)
  for( i <- ints) { println(i)}

  val values1: CustomList[Int] = for(i <- ints; a = (i + 1)) yield a
  println(s"Map Values: $values1")

  val values2 = for {
    i <- ints
    if(i % 2 == 0)
  } yield i

  println(s"With Filter Values: $values2")

  val values3: CustomList[Int] = for(i <- ints; j <- ints; a = (i + j + 1)) yield a
  println(s"FlatMap Values: $values3")
}

 

 

 

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.