12/08/2018, 17:16

Kotlin: Tìm hiểu về function type, function literal

Chào mọi người, trong Kotlin có một cải tiến so với Java là các function trong Kotlin có thể được coi như các biến, điều đó có nghĩa là một function đều có thể được định danh, được gán giá trị, được truyền vào hàm như một argument hoặc là giá trị được trả về từ một hàm khác. Function type Và để ...

Chào mọi người, trong Kotlin có một cải tiến so với Java là các function trong Kotlin có thể được coi như các biến, điều đó có nghĩa là một function đều có thể được định danh, được gán giá trị, được truyền vào hàm như một argument hoặc là giá trị được trả về từ một hàm khác.

Function type

Và để làm được điều đó, các function đó phải có được định danh một kiểu (type) được gọi là function type. Dưới đây là một số ví dụ về function type:

  • ()->Unit: Định danh một function không truyền vào tham số nào và không trả về gì - ví dụ như void funcA() { ... } trong java
  • (Int)->Int: Định danh một function mà có 1 tham số thuộc kiểu integer, và trả về kết quả là một integer, ví dụ trong Java ta có int funA(int arg) { .... }
  • ()->()->Unit: Định danh một function không truyền tham số nào và trả về một function khác với kiểu là ()->Unit.

Ta có thể sử dụng function type giống như một interface và implement funtion type đó như dưới đây:

class MyFunction: ()->Unit {

    override fun invoke() {
        println("I am called")
    }
}

fun main(args: Array<String>) {
    val function = MyFunction()
    function() // Prints: I am called
}

Ta cũng có thể sử dụng chúng như một biến local, properties hay là một arguments:

val greet: ()->Unit
val square: (Int)->Int
val producePrinter: ()->()->Unit

Các biến ở trên hiện tại đang chưa có giá trị nào. Để gán giá trị vào các biến đó, một cách đơn giản nhất là sử dụng Function reference ::, nếu bạn đã dùng đến lambda expression thì chắc cũng có một chút liên hệ với phần này, để gán ta sẽ viết như sau:

fun greetFunction() {
    println("Hello")
}
val greet = ::greetFunction

Function literal

Một cách khác để gán giá trị cho các biến trên là sử dụng Lambda expression hoặc Anonymous function, hai phương pháp này được gọi chung với tên là Function literal.

Lambda expression

Lambda expression là một cách định nghĩa một hàm rất ngắn gọn. Ta sẽ thử gán các giá trị ở phía trên bằng lambda nhé:

val greet: ()->Unit = { println("Hello") }
val square: (Int)->Int = { x -> x * x }
val producePrinter: ()->()->Unit = { { println("I am printing") } }
// Su dung
greet() // Prints: Hello
println(square(2)) // Prints: 4
producePrinter()() // Prints: I am printing

Ở trong phần tham số của square ta có thể rút gọn thay vì truyền trực tiếp (Int)->Int ta có thể truyền kiểu của biến ngay vào bên trong câu lệnh lambda.

Ngoài ra phần tham số của greet và producePrinter có thể bỏ bớt mà vẫn đủ để hiểu kiểu của tham số truyền. Ta có thể rút gọn hàm trên thành như sau:

val greet = { println("Hello") }
val square = { x: Int -> x * x }
val producePrinter = { { println("I am printing") } }

Anonymous function

Anonymous function Cũng là một cách để định nghĩa một function, hãy thử thay ví dụ trên bằng cách sử dụng Anonymous function nhé:

val greet: ()->Unit = fun() { println("Hello") }
val square: (Int)->Int = fun(x) = x * x
val producePrinter: ()->()->Unit = fun() = fun() { println("I am printing") }

Và có thể viết gọn hơn:

val greet = fun() { println("Hello") }
val square = fun(x: Int) = x * x
val producePrinter = fun() = fun() { println("I am printing") }

Sự khác biệt

Nhìn vào 2 ví dụ trên ta có thể thấy lambda expresionanonymous functions khá là giống nhau, vậy vì sao nó lại tách biệt ra làm 2 thành phần?

Điểm khác biệt cơ bản đó là Anonymous function thì rõ ràng hơn so với lambda expression vì giá trị trả về sẽ được quy định rõ ràng còn ở lambda expression thì giá trị trả về sẽ là giá trị cuối cùng trong câu lệnh ở trong hàm hoặc là Unit.

Một câu lệnh mà chưa được gắn nhãn nếu sử dụng lambda sẽ không chạy được như ví dụ sau:

val getMessage = { response: Response ->
    if(response.code !in 200..299) {
        return "Error" // Error! Not allowed
    }
    response.message
}

Để không bị lỗi ta bắt buộc phải sử dụng một nhãn lambda@ trong câu lệnh cuối cùng

val getMessage = lambda@ { response: Response ->
    if(response.code !in 200..299) {
        return@lambda "Error"
    }
    response.message
}

Anonymous function thì giống với các hàm bình thường và cả kiểu trả về và câu lệnh trả về đều cần xác định rõ ràng.

val getMessage = fun(response: Response): String {
    if(response.code !in 200..299) {
        return "Error" // Error! Not allowed
    }
    return response.message
}

Thực tế rằng 2 cách trên đều có thể sử dụng được, nhưng bạn nên sử dụng Anonymous function trong trường hợp return nhiều hơn một, còn lambda expression trong trường hợp là một hàm nhỏ và chỉ return ở một chỗ. Ngoài ra còn một số trường hợp nên dùng Anonymous function như trường hợp sau:

fun greet() = { println("Hello") }
greet() // se in ra cai gi?

Câu trả lời là "Không in ra gì cả, bởi vì greet trả về một hàm thay vì in ra chữ Hello". Vì thế bạn hãy nhớ trường hợp này nên dùng Anonymous function thay vì lambda expression:

fun greet() = fun() { println("Hello") }

Cảm ơn các bạn đã theo dõi bài viết, mong là mọi người có thể hiểu rõ hơn về các cách định nghĩa các hàm trong kotlin và sử dụng nó một cách hợp lý. Bài viết được dịch từ: https://medium.com/@belokon.roman/i-think-it-should-be-mentioned-one-more-case-to-use-lambda-instead-of-anonymous-function-when-we-1f6026878458

0