[Scala] - day 4 : First-Class Functions

Một trong những điều cốt lõi của functional programming là function nên là fisrt-class, đơn giản bạn có thể hiểu nó không chỉ đơn giản được định nghĩa và được invoked (từ này khó dịch sang tiếng Việt quá) mà nó được sử dụng như 1 phần của ngôn ngữ lập trình  như là 1 data type.
Vậy first-class function :
- Có thể được coi như các loại data type khác.
- Có thể được tạo từ 1 literal.
- Được lưu trữ như 1 value ,1 biến hay 1 data structure.
- Được sử dụng như 1 parameter cho 1 function khác.
Vậy ở đây ta có 1 loại function mà dùng function khác như 1 parameter, nó gọi là higher-order functions.
Vậy cụ thể trong scala ta có gì nào.

I- Kiểu dữ liệu Function và Values

Kiểu dữ liệu của function được diễn tả bằng 1 cặp bao gồm 1 nhóm input type và output type ,chúng được nối với nhau bằng 1 mũi tên (=>) ,nhìn vào cấu trúc này chắc không cần giải thích cho bạn bên nào nào input và bên nào là output.
Syntax:
([<type>, . . .]) => <type>
 Ví dụ function nhanDoi(x: Int) : Int = x*2 có kiểu là (Int) => Int :
scala> def nhanDoi(x: Int) : Int = x*2
nhanDoi: (x: Int)Int
scala> nhanDoi(5)
res0: Int = 10
scala> val firstClass: (Int) => Int = nhanDoi
firstClass: Int => Int = <function1>
scala> firstClass(5)
res1: Int = 10
- firstClass ở ví dụ trên là 1 value, nhưng không như các value khác, nó được invoke.
- invoke firstClass cũng như invoke nhanDoi ta thu được chung 1 kết quả.
- 1 function value có thể gán cho 1 value như 1 data type.
=> bạn thấy điều gì hay chưa, 1 value có thể thay đổi gía trị :)

Đôi khi bạn sẽ gặp vấn đề khi function có nhiều biến và chúng mang rất nhiều kiểu và việc gán biến mất thời gian, chúng ta có 1 cách khác để gán 1 function vào 1 biến.
Syntax:
val <identifier> = <function name> _
Chúng ta tạo lại 1 function mới và thử gán bằng cả 2 cách :
scala> def max(a: Int, b: Int) = if (a > b) a else b
max: (a: Int, b: Int)Int 
scala> val maximize: (Int, Int) => Int = max
maximize: (Int, Int) => Int = <function2> 
scala> val maximize = max _
maximize: (Int, Int) => Int = <function2>
scala> maximize(50, 30)
res3: Int = 50

II - Higher-Order Functions

Như bạn đã biết,function trong scala có thể sử dụng như 1 parameter cho 1 function,và 1 function dùng function khác để như 1 parameter được gọi là higher order function.
Thử vài ví dụ ,giờ ta sẽ hiên số tiền trong tài khoản của 1 ai đó,quy định là hiện VND nhưng tài khoản anh kia chỉ có Dollar :
scala> def dollar2Vnd(number: Int): Int = number * 21750
dollar2Vnd: (number: Int)Int
scala> def soDu(soTienDo : Int,f : (Int) => Int){
             | println(f(soTienDo)+" VND")
             | }
soDu: (soTienDo: Int, f: Int => Int)Unit
scala> soDu(5,dollar2Vnd)
108750 VND

III - Function Literals

Trước khi nêu ra định nghĩa về function literal ta cùng xem ví dụ sau :
scala> val maximize = (a: Int, b: Int) => if (a > b) a else b
maximize: (Int, Int) => Int = <function2>
Như bạn thấy tôi khởi tạo 1 value và gán cho nó 1 function nhưng tôi không định nghĩa trước function đó,ở đây ta gọi  (a: Int, b: Int) => if (a > b) a else b  là 1 function literal.
Vậy 1 function literal có những đặc điểm sau :

 - Cú pháp :
([<identifier>: <type>, ... ]) => <expression>
 - Không có định danh (Anonymous functions)

IV - By-Name Parameters

Đến đây bạn đã biết trong Scala 1 function có thể được dùng như 1 parameter , và nó là 1 parameter kiểu function ,như ở các ví dụ trên ta truyền vào 1 function và gọi lại function này trong function chính,đôi khi bạn chỉ quan tâm đến giá trị cuối cùng function này trả về chứ không quan trọng nó chạy ra sao.
Với cách trên :
scala> def nhanDoi (x : Int => Int) : Int = x * 2
<console>:10: error: value * is not a member of Int => Int
def nhanDoi (x : Int => Int) : Int = x * 2
                                                        ^
Bạn sẽ thấy nó báo rằng x là 1 function và nó không thể nhân với 2.Ta khai báo lại và nói với hệ thống rằng ta chỉ quan tâm đến output :
scala> def nhanDoi (x : => Int) : Int = x * 2
nhanDoi: (x: => Int)Int
scala> nhanDoi(4)
res20: Int = 8
Cụ thể hơn cùng xem ví dụ sau  :
scala> def functionParam(i : Int) = {println("message từ function : functionParam");i}
functionParam: (i: Int)Int
scala> def nhanDoi(x : => Int) = {
        |   println("message từ function : nhanDoi" + x)
        |   x*2
        | }
nhanDoi: (x: => Int)Int
scala> nhanDoi(functionParam(7))
message từ function : functionParam
message từ function : nhanDoi7
message từ function : functionParam
res0: Int = 14
Mỗi khi ta gọi x từ function nhanDoi thì functionParam lại được tính toán.

V - Placeholder Syntax

Từ những phần trên và 1 số bài viết trước bạn cũng đã biết đến placeholder ( _ ), hôm nay mình cùng xem những công dụng còn lại của nó.
Nói tóm lại placeholder có thể được dùng vào 2 mục đích sau :
 - Thay thể cho những function type được định nghĩa rõ ràng
 - Thay thế cho những parameter được sử dụng không quá 1 lần.
Có lẽ vẫn hơi khó hiểu,ta cùng xem ví dụ :
scala> val nhanDoi: Int => Int = _ * 2
nhanDoi: Int => Int = <function1>
scala> nhanDoi(5)
res7: Int = 10
Ở đây ta gán 1 function literal cho biến nhanDoi với cấu trúc <  Int => Int = _ * 2  > , placeholder ở đây ( _ ) thay thế cho toàn bộ input của function vì như bạn thấy ở cấu trúc ta chỉ có 1 input kiểu Int được truyền vào và Scala tự hiểu _ thay cho input đó.
Một ví dụ khác, ta có function để hiện thị số dư tài khoản 1 người từ dollar ra vnd ở trên , thử dùng lại với placeholder : 
scala> soDu(5, _ * 21750)
108750 VND
Ta truyền vào 1 function literal ứng với cấu trúc đã định nghĩa là 1 input kiểu Int và output là 1 số Int, placeholder ( _ ) được hiểu là chính input của function.
Thử với 1 trường hợp khác mà ta có nhiều input và thứ tự các input là quan trọng,  giả sử ta cần kiểm tra 3 số xem số đầu tiên có lớn hơn 2 số kia cộng lại :
scala> def tripleOp(a: Int, b: Int, c: Int, f: (Int, Int, Int) => Boolean) = f(a,b,c)
tripleOp: (a: Int, b: Int, c: Int, f: (Int, Int, Int) => Boolean)Boolean
scala> tripleOp(93, 92, 14, _ > _ + _)
res9: Boolean = false
Có thể viết đơn giản thế này : 
scala> def kiemTra3So: (Int,Int,Int) => Boolean = _ > _ + _
kiemTra3So: (Int, Int, Int) => Boolean
scala> kiemTra3So(93, 92, 14)
res10: Boolean = false
Partially Applied Functions
Giả sử bạn có 1 hàm kiểm tra số này có phải là bội số của số kia không :
scala> def boiSo(x: Int, y: Int) : Boolean = y % x == 0
boiSo: (x: Int, y: Int)Boolean
scala> boiSo(2,13)
res16: Boolean = false
Giờ bạn cần kiểm tra 1 số có fải là bôi số của 3 hay không ,đơn giản ta gọi hàm boiSo(3, số_cần_kt) , bạn thấy số 3 lập đi lập lại và không muốn viết lại nhiều lần :
 - Viết 1 hàm mới
 - Tạn dụng điểm mạnh của placeholder (_) và function type
Hãy thử cách 2 :
scala> val chiaHetCho3 = boiSo(3, _ : Int)
chiaHetCho3: Int => Boolean = <function1>
scala> chiaHetCho3(21)
res17: Boolean = true
scala> chiaHetCho3(20)
res18: Boolean = false
Hay ban cũng có thể dùng group parameter :
scala> def boiSo(x: Int)(y: Int) : Boolean = y % x == 0
boiSo: (x: Int)(y: Int)Boolean
scala> val chiaHetCho3 = boiSo(3) _
chiaHetCho3: Int => Boolean = <function1>
scala> chiaHetCho3(20)
res19: Boolean = false 
Hôm nay quá dài rồi,hẹn gặp các bạn hôm sau với Collections trong Scala - List, Map và Set


Comments

  1. Cảm ơn bạn, bài viết khá hay, mình cũng học được thêm nhiều điều từ đây

    ReplyDelete

Post a Comment