The For-Comprehension in Scala

Hôm nay mình sẽ giới thiệu một Special Notation trong Scala đó là For Comprehension. Không đơn thuần chỉ là xử lý lặp trong một List mà nó còn cung cấp cho chung ta các lựa chọn lọc (Filtering Options) hay tạo ra một List mới. Hãy cùng nhau tìm hiểu qua các ví dụ. Trước tiên chúng ta xem cách giải thích từ trang http://docs.scala-lang.org/tutorials/FAQ/yield.html

Ví dụ 1:

for(x <- c1; y <- c2; z <-c3) {???}

Tương đương (Được dịch ra):

c1.foreach(x => c2.foreach(y => c3.foreach(z => {???})))

Ví dụ 2:

for(x <- c1; y <- c2; z <- c3) yield {???}

Tương đương (Được dịch ra):

c1.flatMap(x => c2.flatMap(y => c3.map(z => {???})))

Ví dụ 3:

for(x <- c; if cond) yield {???}

Tương đương (Được dịch ra):

c.withFilter(x => cond).map(x => {???})

Ví dụ 4:

for(x <- c; y = ...) yield {???}

Tương đương (Được dịch ra):

c.map(x => (x, ...)).map((x,y) => {???})

Từ bốn ví dụ trên ta có thể thấy việc sử dụng For-Comprehension code sẽ đơn giản hơn rất nhiều.
Tiếp theo chúng ta cùng nhau tìm hiểu về cách sử dụng For-Comprehension. Giả sử chúng ta xây dựng chương trình quản lý bóng đá (Đang mùa Euro 2016 nên chọn chủ đề này cho nóng), có một danh sách cách các siêu sao:

case class Player(name: String, position: String, salary: Float)
val players = List(Player("CR7", "striker", 200000), Player("Messi", "Striker", 200000),
      Player("Andrea Pirlo", "midfielder", 100000), Player("Sergio Ramos", "Defender", 100000))

Hệ thống cần tìm ra những chơi ở vị trí tiền đạo cắm(striker):

for {
      player <- players
      if player.position equalsIgnoreCase("striker")
    } yield player.name //> List(CR7, Messi)

Một cầu thủ có thể chơi ở nhiều vị trí như tiền đạo, tiền vệ, chúng ta sẽ sửa lại chương trình một chút:

case class Player(name: String, position: List[String], salary: Float)
val players = List(Player("CR7", List("striker", "midfielder"), 200000), Player("Messi", List("striker", "midfielder"), 200000),
      Player("Andrea Pirlo", List("midfielder", "Defender"), 100000), Player("Sergio Ramos", List("Defender", "midfielder"), 100000))

Hệ thống cần tìm danh sách các cầu thủ có thể chơi ở vị trí hậu vệ (defender):

for {
      player <- players
      position <- player.position
      if position equalsIgnoreCase("defender")
    } yield player.name //> List(Andrea Pirlo, Sergio Ramos)

OK! Chúng ta vừa tìm hiểu về Filtering Options, tiếp theo chúng ta sẽ cùng tìm hiểu một ví dụ sử dụng For-Comprehension với kiểu Option. Giả sử bạn muốn tổ chức một seminar để chia sẻ kiến thức về Funtional Programming, điều kiện đầu tiên đó là: tại thời điểm bạn muốn tổ chức máy chiếu và phòng họp có sẵn sàng không.

case class Projector()
case class MeetingRoom()
case class Precondition()
def getProjector: Option[Projector]
def getMeetingRoom: Option[MeetingRoom]
def getPrecondition(projector: Projector, meetingRoom: MeetingRoom): Option[Precondition]

def holdSeminar = {
    val possibleSeminar: Option[Precondition] =
      for {
        projector <- getProjector
        meetingRoom <- getMeetingRoom
        precondition <- getPrecondition(projector, meetingRoom)
      } yield {
        precondition
      }

    possibleSeminar getOrElse cancelSeminar
  }

Cuối cùng, mình sẽ giới thiệu với các bạn một cách sử dụng For-Comprehension với Try. Giả sử bạn là nhà quản lý bóng đá, để chuẩn bị cho mùa giải mới bạn cần bổ sung lực lượng cho câu lạc bộ, và yêu cầu đê ra là các cầu thủ mua về phải dưới 27 tuổi, và giá của cầu thủ không vượt quá 10 triệu USD. Chúng ta sẽ cùng nhau xây dựng chương trình nhé!

case class Player(name: String, age: Int, price: Float)

Chúng ta sẽ thực hiện việc kiểm tra những cầu thủ phù hợp với tiêu chuẩn tuyển dụng thông qua method validation.

def validation(player: Player) = {
    def validAge(age: Int): Try[Int] = {
      if(age > 27) {
        Failure(new IllegalArgumentException("To old."))
      } else {
        Success(age)
      }
    }

    def validPrice(price: Float): Try[Float] = {
      if(price > 10000000) {
        Failure(new IllegalArgumentException("To expensive."))
      } else {
        Success(price)
      }
    }

    for {
      age <- validAge(player.age)
      price <- validPrice(player.price)
    } yield player

  }

Hãy xem hàm validation trả về kết quả như nào với các cầu thủ phù hợp và không phù hợp:

val cr7 = new Player("CR7", 22, 10000)
val messi = new Player("messi", 25, 1000000000)
validation(cr7) //> Success(Player(CR7,22,10000.0))
validation(messi) //>Failure(java.lang.IllegalArgumentException: To expensive.)

Sử dụng For-Comprehension giúp cho source code tinh gọn hơn rất nhiều, giúp chúng ta tiết kiệm được thời gian. Nhưng cũng cần phải hiểu rõ bản chất để sử dụng cho phù hợp, nếu không độ phức tạp của logic xử lý sẽ tăng cao ảnh hưởng đến performance của hệ thống.

Những ví dụ trên phần nào có thể giúp bạn hiểu được cách sử dụng và lợi ích của việc sử dụng For-Comprehension trong Scala. Hy vọng sẽ nhận được sự góp ý!

Add a Comment

Scroll Up