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
.
1 2 3 |
cd Documents && cd learn-elm elm reactor |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="token keyword">module</span> <span class="token constant">Tell</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">day</span><span class="token punctuation">)</span> <span class="token hvariable">day</span> <span class="token operator">:</span> <span class="token constant">Int</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token hvariable">day</span> <span class="token hvariable">n</span> <span class="token operator">=</span> <span class="token keyword">case</span> <span class="token hvariable">n</span> <span class="token keyword">of</span> <span class="token number">0</span> <span class="token operator">-></span> <span class="token string">"Sunday"</span> <span class="token number">1</span> <span class="token operator">-></span> <span class="token string">"Monday"</span> <span class="token number">2</span> <span class="token operator">-></span> <span class="token string">"Tuesday"</span> <span class="token number">3</span> <span class="token operator">-></span> <span class="token string">"Wednesday"</span> <span class="token number">4</span> <span class="token operator">-></span> <span class="token string">"Thursday"</span> <span class="token number">5</span> <span class="token operator">-></span> <span class="token string">"Friday"</span> <span class="token number">6</span> <span class="token operator">-></span> <span class="token string">"Saturday"</span> _ <span class="token operator">-></span> <span class="token string">"Unknown"</span> |
Đ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à:
1 2 3 4 5 6 7 8 9 |
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.
1 2 3 4 5 6 7 |
<span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">main</span><span class="token punctuation">)</span> <span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span> <span class="token import-statement"><span class="token keyword">import</span> Tell <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span> <span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">Html</span> <span class="token hvariable">message</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token punctuation">(</span><span class="token hvariable">Tell.day</span> <span class="token number">0</span><span class="token punctuation">)</span> |
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…
1 2 3 4 5 6 7 8 9 |
<span class="token keyword">module</span> <span class="token constant">Tell</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">any</span><span class="token punctuation">,</span> <span class="token hvariable">day</span><span class="token punctuation">)</span> <span class="token hvariable">any</span> <span class="token operator">:</span> <span class="token constant">Maybe</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token hvariable">any</span> <span class="token hvariable">value</span> <span class="token operator">=</span> <span class="token keyword">case</span> <span class="token hvariable">value</span> <span class="token keyword">of</span> <span class="token constant">Just</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token string">"Something"</span> <span class="token constant">Nothing</span> <span class="token operator">-></span> <span class="token string">"Nothing"</span> <span class="token comment">-- day : ...</span> |
1 2 3 |
<span class="token comment">-- ...</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token punctuation">(</span><span class="token hvariable">Tell.any</span> <span class="token punctuation">(</span><span class="token constant">Just</span> <span class="token number">1001</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
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ỳ.
1 2 3 4 5 6 7 8 9 10 11 |
<span class="token keyword">module</span> <span class="token constant">Tell</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">list</span><span class="token punctuation">,</span> <span class="token hvariable">any</span><span class="token punctuation">,</span> <span class="token hvariable">day</span><span class="token punctuation">)</span> <span class="token hvariable">list</span> <span class="token operator">:</span> <span class="token constant">List</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token hvariable">list</span> <span class="token hvariable">l</span> <span class="token operator">=</span> <span class="token keyword">case</span> <span class="token hvariable">l</span> <span class="token keyword">of</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">-></span> <span class="token string">"Empty"</span> <span class="token punctuation">[</span><span class="token hvariable">x</span><span class="token punctuation">]</span> <span class="token operator">-></span> <span class="token string">"Exactly One"</span> <span class="token hvariable">x</span><span class="token operator">::</span><span class="token hvariable">xs</span> <span class="token operator">-></span> <span class="token string">"X and other Xs"</span> <span class="token comment">-- any : ...</span> <span class="token comment">-- day : ...</span> |
1 2 3 |
<span class="token comment">-- ...</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token punctuation">(</span><span class="token hvariable">Tell.list</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token number">9</span><span class="token punctuation">]</span><span class="token punctuation">)</span> |
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.
Cùng lúc
matching
bằng các giá trị cụ thể đặt trong cácpattern
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 –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="token keyword">module</span> <span class="token constant">Tell</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span> <span class="token hvariable">position</span> <span class="token punctuation">,</span> <span class="token hvariable">list</span> <span class="token punctuation">,</span> <span class="token hvariable">any</span> <span class="token punctuation">,</span> <span class="token hvariable">day</span> <span class="token punctuation">)</span> <span class="token hvariable">position</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">Int</span><span class="token punctuation">,</span> <span class="token constant">Int</span><span class="token punctuation">,</span> <span class="token constant">Int</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token hvariable">position</span> <span class="token hvariable">coordinates</span> <span class="token operator">=</span> <span class="token keyword">case</span> <span class="token hvariable">coordinates</span> <span class="token keyword">of</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token string">"Root of the Universe"</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> _<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token string">"On the X-axis"</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token string">"On the Y-axis"</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> _<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token string">"On the Z-axis"</span> _ <span class="token operator">-></span> <span class="token string">"Roaming the Universe"</span> <span class="token comment">-- list : ...</span> <span class="token comment">-- any : ...</span> <span class="token comment">-- day : ...</span> |
1 2 3 |
<span class="token comment">-- ...</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token punctuation">(</span><span class="token hvariable">Tell.position</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
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.
1 2 3 4 5 6 7 |
<span class="token keyword">module</span> <span class="token constant">Recursion</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">sumIntList</span><span class="token punctuation">)</span> <span class="token hvariable">sumIntList</span> <span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">Int</span> <span class="token operator">-></span> <span class="token constant">Int</span> <span class="token hvariable">sumIntList</span> <span class="token hvariable">range</span> <span class="token operator">=</span> <span class="token keyword">case</span> <span class="token hvariable">range</span> <span class="token keyword">of</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">-></span> <span class="token number">0</span> <span class="token hvariable">first</span><span class="token operator">::</span><span class="token hvariable">rest</span> <span class="token operator">-></span> <span class="token hvariable">first</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token hvariable">sumIntList</span> <span class="token hvariable">rest</span><span class="token punctuation">)</span> |
1 2 3 4 5 6 7 |
<span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">main</span><span class="token punctuation">)</span> <span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span> <span class="token import-statement"><span class="token keyword">import</span> Recursion <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span> <span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">Html</span> <span class="token hvariable">message</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token punctuation">(</span><span class="token hvariable">String.fromInt</span> <span class="token punctuation">(</span><span class="token hvariable">Recursion.sumIntList</span> <span class="token punctuation">(</span><span class="token hvariable">List.range</span> <span class="token number">0</span> <span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
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.
1 2 3 |
<span class="token comment">-- ...</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token operator"><|</span> <span class="token hvariable">String.fromInt</span> <span class="token operator"><|</span> <span class="token hvariable">Recursion.sumIntList</span> <span class="token operator"><|</span> <span class="token hvariable">List.range</span> <span class="token number">0</span> <span class="token number">9</span> |
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. Mình thì chọn giữ nguyên thứ tự viết tên các sub-program
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.
1 2 3 4 5 6 |
<span class="token comment">-- ...</span> <span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Html.text</span> <span class="token operator"><|</span> <span class="token hvariable">String.fromInt</span> <span class="token operator"><|</span> <span class="token hvariable">Recursion.sumIntList</span> <span class="token operator"><|</span> <span class="token hvariable">List.range</span> <span class="token number">0</span> <span class="token number">9</span> |
Recursion in JS
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="token comment">// -- main : any => null</span> <span class="token keyword">const</span> <span class="token function-variable function">main</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">_</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span> <span class="token punctuation">(</span><span class="token function">sumNumberArray</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token number">9</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// -- sumNumberArray : [number] -> number</span> <span class="token keyword">const</span> <span class="token function-variable function">sumNumberArray</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">numberArray</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">[</span>first<span class="token punctuation">,</span> <span class="token operator">...</span>rest<span class="token punctuation">]</span> <span class="token operator">=</span> numberArray <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>numberArray<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span> <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"any-other-case"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> first <span class="token operator">+</span> <span class="token function">sumNumberArray</span> <span class="token punctuation">(</span>rest<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// -- start program</span> <span class="token function">main</span> <span class="token punctuation">(</span><span class="token number">Infinity</span><span class="token punctuation">)</span> |
[Declarative Programming + Elm] Bài 8 – Conditional Expression