- vừa được xem lúc

[Functional Programming + Elm] Bài 4 - Functor & Applicative

0 0 10

Người đăng: Semi Dev

Theo Viblo Asia

Đối với bất kỳ ngôn ngữ lập trình nào khi chúng ta đã nắm bắt được các kiểu dữ liệu cơ bản và cú pháp hỗ trợ khai báo trừu tượng Abstraction để tạo ràng buộc khi thiết kế tổng quan phần mềm, thì bước tiếp theo là tìm hiểu về một số dạng thức triển khai pattern để áp dụng cho tiến trình thiết kế. Trong môi trường Functional nói riêng thì chúng ta có một số pattern rất đơn giản và phổ biến được triển khai nhờ các class mà chúng ta đã nói tới trước đó. Và pattern đầu tiên mà chúng ta sẽ tìm hiểu là có tên là Functor.

Functor

Xuất phát với hàm map mà chúng ta đã biết trong module List, thực tế thì đây là hàm phổ biến nhất trong Functional bởi có rất nhiều trường hợp chúng ta sẽ không viết những lời gọi hàm trực tiếp làm việc với kiểu dữ liệu nào đó. Tức là thay vì trực tiếp gọi hàm Char.toCode và truyền vào một giá trị c : Char thì chúng ta lại viết là Char.map f c với f được truyền tới từ đâu đó và có thể f sẽ là Char.toCode.

Bằng cách sử dụng map làm HOD và điều khiển việc gọi hàm trên c : Char như vậy thì chúng ta sẽ có thể kiến trúc chương trình linh động hơn. Và kiểu Char nếu được định nghĩa hàm map để có thể viết code triển khai như vậy thì sẽ được gọi là một kiểu thuộc class Functor.

Functorclass bao gồm các kiểu dữ liệu có thể được đối chiếu bởi hàm map.

Trong Haskell hay PureScript thì Type Class được hỗ trợ ở cấp độ cú pháp và có rất nhiều Type Class đã được định nghĩa sẵn trong thư viện tiêu chuẩn, bao gồm cả Functor mà chúng ta đang nói tới ở đây. Bây giờ chúng ta sẽ định nghĩa class Functor cho bất kỳ kiểu dữ liệu a nào tham gia vào sẽ có thể map để đối chiếu các giá trị tới kiểu b bất kỳ.

module Class exposing (YesNo, Functor) type alias Functor a b c d = { map : (a -> b) -> c -> d } -- type alias YesNo ...

Và code triển khai để sử dụng vẫn class Functor sẽ được viết trong module Book ở ví dụ trước đó.

module Book exposing (Book, yesno, map)
import Class exposing (..) type alias Book = { title : String , author : String , rating : Float } instanceFunctor : Class.Functor Book any Book any
instanceFunctor = Class.Functor map map : (Book -> any) -> Book -> any
map func abook = func abook -- instanceYesNo ...

Về việc gắn các thông tin định kiểu ở dòng instanceFunctor : Class.Functor Book any Book any cho các vị trí Type Variable thì ban đầu chúng ta có thể viết instanceFunctor : Class.Functor a b c d. Sau đó cứ tiến hành viết code triển khai code hàm map trước để suy nghĩ về thao tác khi sử dụng hàm map.

Ở đây chúng ta có thể thiết kế để map nhận vào hàm func không có hiểu biết gì về kiểu Book và chỉ làm việc trên các kiểu primitive và sau đó việc áp dụng func cho trường dữ liệu nào sẽ do logic của map quy định; Hoặc có thể thiết kế để map nhận vào hàm func chứa logic làm việc trực tiếp với kiểu Book như trên.

Sau đó chúng ta đặt các thông tin định kiểu tương ứng của hàm map : (a -> b) -> c -> d ngược lại về định nghĩa của instanceFunctor để đảm bảo logic triển khai class Functor được nhất quán và trình biên dịch sẽ không báo lỗi.

Và như vậy là chúng ta đã có thể sử dụng Book.map ở bất kỳ vị trí nào trong chương trình và truyền vào một lambda đối chiếu mà không cần viết code định nghĩa thêm các hàm đối chiếu trong module Book.

module Main exposing (main)
import Book exposing (..)
import Html exposing (Html, text) main : Html message
main = let yogaBook = (Book "Yoga" "Patanjali" 9.9) showRating = (\abook -> "Rating: " ++ String.fromFloat abook.rating) in text <| Book.map showRating yogaBook -- "Rating: 9.9"

Đó là Functor. Rất đơn giản và linh động. Bây giờ chúng ta hãy nói về Applicative.

Applicative

Khái niệm Applicative có tên gọi đầy đủ là Applicative Functor, và được sử dụng để nói về các kiểu Functor là các kiểu cấu trúc dữ liệu có thể lưu trữ một hoặc nhiều hàm f khác nhau.

Ví dụ như một Maybe có thể có chứa một hàm (number -> number) để tương tác với các giá trị số học.

> increment = (+)1
<function> : number -> number > maybeFunc = Just increment
Just <function> : Maybe (number -> number)

À... và điều kiện kèm theo là maybeFunc như mô tả ở trên cần phải có thể được áp dụng lên một maybeNumb để trả về một giá trị cùng kiểu Maybe number như sau:

> maybeNumb = Just 9
Just 9 : Maybe number > Maybe.apply maybeFunc maybeNumb
-- Error: cannot find `Maybe.apply`
-- Expected: Just 10 : Maybe number

Chúng ta có thể đọc thao tác Maybe.apply ở đây là - áp dụng logic hàm lưu trữ trong maybeFunc lên giá trị lưu trữ trong maybeNumb. Và trong trường hợp này thì kiểu dữ liệu Maybe được xem là một Applicative Functor hay là một thành viên của class Applicative.

Như vậy là chúng ta có Applicative là các kiểu dữ liệu dạng vỏ bọc wrapper như Maybe, List, v.v... có thể lưu trữ các hàm có tên hoặc lambda và tạo tương tác với chính kiểu wrapper đó bằng hàm apply. Logic xử lý của apply ở đây là tách lấy các hàm f đang được lưu trữ trong wrapper đầu tiên và map sang giá trị đang được lưu trữ trong wrapper thứ hai.

Phần code ví dụ ở trên chỉ là để mô phỏng cú pháp sử dụng và định nghĩa Applicative, còn trên thực tế thì chúng ta không có hàm apply trong module Maybe để sử dụng như vậy. Việc viết thêm các hàm mở rộng cho module Maybe là không khả thi trong môi trường Elm và chúng ta sẽ phải tạo ra một module MaybeExt để mở rộng thêm tính năng cho kiểu Maybe sẵn có.

Tuy nhiên trước hết hãy bắt đầu với việc định nghĩa class Applicative trong Elm bằng record như chúng ta đã định nghĩa class Functor trước đó.

module Class exposing (YesNo, Functor, Applicative) type alias Applicative a b c d e = { apply : a -> b -> c , map : (d -> e) -> b -> c } -- type alias Functor ...
-- type alias YesNo ...

Ở đây chúng ta vẫn có yếu tố kế thừa từ Functor là hàm map, tuy nhiên trong Applicative thì apply quan trọng hơn và sẽ được sử dụng làm mốc triển khai logic trước. Bây giờ chúng ta thực hiện khai báo instance trong module MaybeExt và viết code chi tiết cho applymap để có kết quả hoạt động như dự kiến.

module MaybeExt exposing (map, apply)
import Class exposing (..) instanceApplicative : Applicative a b c d e
instanceApplicative = Applicative apply map

Điểm đầu tiên cần lưu ý trong code triển khai là chúng ta có hàm maybeFunc chỉ đảm nhiệm vai trò làm việc với giá trị được đặt bên trong kiểu wrapper và không có hiểu biết gì về cấu trúc của wrapper được sử dụng là Maybe, List, hay Record, v.v...

-- instance ... apply : Maybe (a -> b) -> Maybe a -> Maybe b
apply maybeFunc maybeAny = case maybeFunc of Nothing -> maybeAny Just func -> map func maybeAny

Như vậy hàm apply : a -> b -> c khai báo trong class Applicative sẽ có thông tin định kiểu cụ thể là tham số đầu tiên có dạng hàm (a -> b) được đặt trong wrapper Maybe. Logic xử lý của apply sẽ nhận vào một Maybe tiếp theo có chứa dữ liệu là giá trị thuộc kiểu a nào đó tương thích với maybeFunc. Và kết quả trả về là một Maybe mới chứa giá trị thuộc kiểu b cũng tham chiếu từ maybeFunc.

Lúc này chúng ta đã có thể viết các thông tin định kiểu cụ thể này vào vị trí khai báo instance và vẫn để các Type Variable còn lại là de chưa biết chính xác. Sau đó tiếp tục viết code cho hàm map:

-- apply : ... map : (a -> b) -> Maybe a -> Maybe b
map func maybeAny = case maybeAny of Nothing -> Nothing Just any -> Just (func any)

Xuất phát từ vị trí Functor là tham số thứ hai Maybe a để đối chiếu tới giá trị mới là Maybe b. Như vậy hàm func sẽ có thông tin định kiểu là (a -> b) để có logic phù hợp. Và chúng ta đang có khai báo trong class Applicativemap : (d -> e) -> b -> c. Như vậy tổng kết lại chúng ta sẽ có thông tin định kiểu đầy đủ cho thao tác khai báo instance... là:

instanceApplicative : Applicative (Maybe (a -> b)) (Maybe a) (Maybe b) a b
instanceApplicative = Applicative apply map

Bây giờ chúng ta đã có thể sử dụng elm reactor hoặc elm repl để kiểm tra hoạt động của apply.

> maybeFunc = Just ((+) 1)
Just <function> : Maybe (number -> number) > maybeAny = Just 9
Just 9 : Maybe number > MaybeExt.apply maybeFunc maybeAny
Just 10 : Maybe number

Một ví dụ khác về Applicative là tạo apply cho kiểu List. Giả sử chúng ta có một funcList chứa các hàm func : (number -> number) và một numbList khác chứa các giá trị number như sau:

> add : number -> number -> number
> add a b = a + b > funcList = [add 1,add 2,add 3,add 4,add 5,add 6,add 7,add 8,add 9]
> [ ... ] : List (number -> number) > numbList = [ 8, 7, 6, 5, 4, 3, 2, 1, 0]
> [ ... ] : List number

Với dạng thức triển khai Applicative như trên thì chúng ta sẽ có thể tạo apply để áp dụng các hàm ở funcList lên các giá trị ở numbList với tỉ lệ 1:1 và trả về mảng kết quả như sau:

> ListExt.apply funcList numbList
[9,9,9,9,9,9,9,9,9] : List number

Logic của map ở đây vẫn sẽ duy trì giống như List.map sẵn có và không cần phải định nghĩa lại. Tuy nhiên apply thì sẽ có khá nhiều thao tác cần thực hiện thêm so với trường hợp của Maybe để có logic hoạt động như vậy. Bạn có thể sử dụng trường hợp này để luyện tập triển khai Applicative và chúng ta sẽ tạm dừng tại đây để chuyển sang những khái niệm Functional tiếp theo.

(chưa đăng tải) [Functional Programming + Elm] Bài 5 - Monoid & Monad

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 51

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 31

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 27

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 47

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 266

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 35