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

[Declarative Programming + Elm] Bài 7 - Pattern Matching & Recursion

0 0 12

Người đăng: Semi Art

Theo Viblo Asia

Sau khi đã điểm qua xong những công cụ hỗ trợ thao tác với các kiểu dữ liệu căn bản, chúng ta tiếp tục tìm đến nhóm công cụ hỗ trợ tạo logic xử lý linh động cho code tùy vào trạng thái của dữ liệu nhận được và lặp các thao tác xử lý trên tập dữ liệu. Và ở bài viết này thì chúng ta sẽ cần chạy thử code của các sub-program, do đó nên việc tương tác với kết quả code chúng ta sẽ thực hiện với elm reactor.

cd Documents && cd learn-elm
elm reactor

http://localhost:8000/src/

Pattern Matching

Đầu tiên là cấu trúc logic có tên gọi là Pattern Matching, thường được xem là tương đương với cấu trúc lệnh điều kiện hay rẽ nhánh trong môi trường Imperative. Tên gọi này có hai từ và chúng ta sẽ quan tâm tới yếu tố Matching trước. Các ngôn ngữ thuần Declarative gọi cấu trúc logic rẽ nhánh là Matching (sự đối chiếu) là bởi vì chương trình của chúng ta sẽ không có các câu lệnh tuần tự mà thay vào đó là các định nghĩa song song.

module Tell exposing (day) day : Int -> String
day n = case n of 0 -> "Sunday" 1 -> "Monday" 2 -> "Tuesday" 3 -> "Wednesday" 4 -> "Thursday" 5 -> "Friday" 6 -> "Saturday" _ -> "Unknown"

Đoạn định nghĩa day n như trên sẽ được trình biên dịch compiler đọc lần lượt là:

day 0 = "Sunday"
day 1 = "Monday"
day 2 = "Tuesday"
day 3 = "Wednesday"
day 4 = "Thursday"
day 5 = "Friday"
day 6 = "Saturday"
day _ = "Unknown"

Riêng vị trí cuối cùng thì ký hiệu _ sẽ được đọc là những giá trị khác của n. Bây giờ chúng ta sẽ sửa lại chương trình main và xem kết quả chạy thử code Tell.day trên trình duyệt.

module Main exposing (main)
import Html exposing (..)
import Tell exposing (..) main : Html message
main = Html.text (Tell.day 0)

http://localhost:8000/src/Main.elm

Ok. như vậy là chúng ta đang có một cú pháp dạng switch..case hoạt động tốt. Tuy nhiên bạn thấy đấy, điểm khác biệt ở đây là, ở phía bên phải của các case đều là các giá trị value, chứ không phải là các câu lệnh statement. Cú pháp case..of mà chúng ta thấy ở đây, là một dạng biểu thức liên hệ để thay thế cho việc viết lại nhiều lần tên của sub-program trong định nghĩa như phần giải thích phía trên. Vì vậy người ta sử dụng từ Matching (đối chiếu) thay cho từ conditional (điều kiện) trong môi trường Imperative.

Thế còn Pattern thì sao ?

Từ đó có nghĩa là dạng thức - tức là chúng ta sẽ có thể đối chiếu bằng các dạng thức của dữ liệu chứ không nhất thiết phải là các giá trị cụ thể. Ví dụ như tên định kiểu của giá trị nhận được, hoặc dạng thức mô tả các trạng thái của các cấu trúc dữ liệu - ví dụ List rỗng, 1 phần tử, 2 phần tử, hoặc nhiều hơn, v.v...

module Tell exposing (any, day) any : Maybe a -> String
any value = case value of Just a -> "Something" Nothing -> "Nothing" -- day : ...
-- ...
main = Html.text (Tell.any (Just 1001))

http://localhost:8000/src/Main.elm

Trong trường hợp này, nếu như chúng ta truyền vào Just.any một cái Just chứa bất kỳ giá trị nào thì kết quả hiện thị cũng đều là "Something". Còn nếu truyền vào Tell.any Nothing thì kết quả hiển thị sẽ là chuỗi "Nothing". Điều đó có nghĩa là chương trình con Tell.any chỉ quan tâm tới việc giá trị đó được xếp loại nào trong Maybe, chứ không quan tâm tới câu hỏi giá trị đó là gì? định lượng bao nhiêu? hay có nội dung thế nào?

Chúng ta hãy tiếp tục viết một chương trình con để nhận định trạng thái dữ liệu của một List bất kỳ.

module Tell exposing (list, any, day) list : List a -> String
list l = case l of [] -> "Empty" [x] -> "Exactly One" x::xs -> "X and other Xs" -- any : ...
-- day : ...
-- ...
main = Html.text (Tell.list [0,1,2,3,4,5,6,7,8,9])

Trong code ví dụ thì pattern ở trường hợp cuối cùng là x::xs có nghĩa là khi List l là kết quả của thao tác chèn một phần tử x vào một List xs có chứa các giá trị khác tương tự như x. Điều đó cũng có nghĩa là khi List l có chứa ít nhất 2 phần tử trở lên - bao gồm X và các X khác.

Ở đây nếu như chúng ta thay thế pattern cuối cùng thành [_] - có nghĩa là List l có chứa dữ liệu chứ không rỗng; Thì logic sau khi duyệt qua 2 trường hợp đầu tiên sẽ loại trừ List l rỗng và List l có một phần tử duy nhất, sẽ cho kết quả hoạt động tương đương với x::xs.

Việc sử dụng tên biến cụ thể thay thế cho _, sẽ cho phép chúng ta có thể sử dụng giá trị được phân tách vào biến đó ở phía bên phải của các biểu thức kết quả. Điều này có thể thực hiện được nhờ tính năng destructuring giống với JavaScript mà chúng ta đã nhắc đến trong các bài viết trước.

Cùng lúc matching bằng các giá trị cụ thể đặt trong các pattern có được không ?

Có, chắc chắn là được! Nếu có một số hữu hạn các giá trị đặc biệt cần quan tâm trong logic xử lý của code thì chúng ta có thể đặt các giá trị đó vào vị trí của các biến sử dụng trong các pattern. Lúc này Elm sẽ kiểm tra tính phù hợp của dữ liệu thực tế cả về mặt pattern và giá trị tại vị trí các biến.

Các pattern đối với List như vậy là đã khá linh động rồi. Bây giờ, chúng ta hãy thử làm ví dụ với các Tuple mô tả tọa độ của các điểm trong không gian 3D -

module Tell exposing ( point , list , any , day ) point : (Int, Int, Int) -> String
point coordinates = case coordinates of (0, 0, 0) -> "Root of the Universe" (0, _, _) -> "On the X-axis" (_, 0, _) -> "On the Y-axis" (_, _, 0) -> "On the Z-axis" _ -> "Roaming the Universe" -- list : ...
-- any : ...
-- day : ...
-- ...
main = Html.text (Tell.point (1, 2, 3))

http://localhost:8000/src/Main.elm

Recursion

Sau khi đã biết cách tạo logic linh động cho code dựa trên kiểu và dạng thức của dữ liệu thì chúng ta cần thêm một công cụ nữa để hỗ trợ lặp thao tác trên tập dữ liệu List. Chúng ta đã biết sử dụng công cụ này trước đó rồi. Định nghĩa đệ quy recursion đã được nhắc đến trong bài viết Imperative & Declarative của Series Tự Học Lập Trình Web.

module Recursion exposing (sumIntList) sumIntList : List Int -> Int
sumIntList range = case range of [] -> 0 [x] -> x x::xs -> x + (sumIntList xs)
module Main exposing (main)
import Html exposing (..)
import Recursion exposing (..) main : Html message
main = Html.text (String.fromInt (Recursion.sumIntList (List.range 0 9)))

http://localhost:8000/src/Main.elm

Ậy... viết tới đây thì mình mới để ý là chúng ta đang cần một giải pháp viết gọn lại các lời gọi sub-program xếp chồng nhiều lớp như thế kia. Các cặp ngoặc đơn được đặt trên cùng hàng chữ sẽ khá khó để nhận diện nhanh chóng khi chúng ta đọc lướt qua code.

-- ...
main = Html.text <| String.fromInt <| Recursion.sumIntList <| List.range 0 9

Chỉ đơn giản là cứ mỗi một cặp ngoặc đơn, chúng ta sẽ thay thế bằng một ký hiệu <| để chuyển kết quả của sub-program bên trong ra bên ngoài ở phía bên trái. Bạn cũng có thể sử dụng các ký hiệu |> để sắp xếp thứ tự của các sub-program theo chiều ngược lại hoặc danh sách sổ dọc cũng rất dễ theo dõi. Tuy nhiên, tuần tự viết như vậy sẽ khiến pattern suy luận logic của chúng ta nghiêng về phía Imperative và sẽ ảnh hưởng nhất định tới việc làm quen với tư duy giải quyết các vấn đề theo lối đệ quy recursion đặc biệt quan trọng trong môi trường Declarative.

Lựa chọn của mỗi người có lẽ sẽ khác nhau. Có rất nhiều người chọn |>. Mình thì chọn thứ tự theo chiều đệ quy. Và khi viết liệt kê dạng danh sách sổ dọc thì mỗi ký hiệu <| sẽ có thể hiểu là kết quả thực thi code sẽ được chuyển ngược về dòng trên.

-- ...
main = Html.text <| String.fromInt <| Recursion.sumIntList <| List.range 0 9

Pattern Matching & Recursion in JS

// -- main : any => null
const main = (_) => console.log (sumNumberArray ([1,2,3,4,5,6,7,8,9])) // -- sumNumberArray : [number] -> number
const sumNumberArray = (numberArray) => { var [first, ...rest] = numberArray ; if (numberArray.length == 0) return 0 ; if (numberArray.length == 1) return first ; if ("any-other-case") return first + sumNumberArray(rest) } // -- start program
main (Infinity)

(chưa đăng tải) [Declarative Programming + Elm] Bài 8 - Practicing Recursion

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 52

- 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 32

- 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 28

- 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 48

- 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 36