iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0
Software Development

每天 Racket 3 分鐘系列 第 9

(cond ((not (day-08?)) 'all-right) (else "我若不在寫 Racket,就在去寫 Racket 的路上 — Racket 的控制結構"))

  • 分享至 

  • xImage
  •  

1. 最古老的決策結構

如同我們在前文所見的 ifif 的確是一個最古老的決策結構,幾乎有程式語言的時代,就有了它。而它的使用需要注意什麼呢?在 Racket 裡的 if,先接了判斷值,這判斷值如先前所述,如果不是 #f,就清一色都是 #t。這點其實需要特別注意,因為從程式語言觀點來看,這樣做並不好。一個嚴謹的語言,if 與任何判斷機制,都應只接受 boolean 值。

除此之外,在這裡需要特別說明的,其實是 if 的正論與反論邏輯,if 在其他語言,常常是可以只留判斷式成立時的行為,而不寫 else,像這樣:

private String toUpperCase(String txt){
    if(txt != null){
        return txt.toUpperCase();
    }
}

然而,在 Racket 的 if,卻絕對不能沒有 else 的反論邏輯。例如你不能這樣寫:

(define to-upper-case
  (lambda (txt)
    (if (non-empty-string? txt)
        (string-upcase txt)
        )))  ;; 此處不能沒有第二條敘述!

因此,如果你真的不想做什麼事,可以這麼寫:

(define to-upper-case
  (lambda (txt)
    (if (non-empty-string? txt)
        (string-upcase txt)
        txt)))  ;; 一定要有回傳值

(define display-upper-string
  (lambda (txt)
    (if (non-empty-string? txt)
        (displayln (string-upcase txt))
        (void))))  ;; 或直接用 (void) 結束這區段

而我們使用這些條件判斷時,常需要組合幾個可能,搭配 andornot 之類的邏輯組合方式,而 Racket 正提供了這幾個選項:

(define-values (a b c) (values 1 0 -1))

(if (and (> a 0)
         (= b 0))
    'positive
    'negative)
(if (or (> a 0)
        (> c 0))
    'maybe-positive
    'maybe-negative)
(if (not (> c 0))
    'positive
    'negative)

2. 最前衛的決策框架

當你的邏輯有多重判斷時,在其他的語言,諸如 Java/Python,會使用什麼語法?以 Java 來說,就是使用 else if 的作法,在 Python,我猜應該也不出是這作法。這作法的好處,是在於使用統一的語法形式來表達多層次的判斷,而當然,在寫起來也比較繁瑣。

Racket 則使用另一個簡潔的邏輯控制機制:cond。它可以讓你一直加上條件與回傳值,例外條件則使用 else 來表示。以下是用 cond 寫的一個費氏數列函式:

(define fib
  (lambda (x)
    (cond ((= x 0) 0)
          ((= x 1) 1)
          (else (+ (fib (- n 1)) (fib (- n 2)))))))

然而,cond 是一個特殊的案例,它可以不寫 else 區段,因此若你的 if 沒有 else,或許可以考慮使用 cond 來呼叫。

3. 一切都是為了 (for) 吃糖

下這標題不是把大家當小朋友,而是其實 for 本質上是一種語法糖,它其實並不存在於程式語言的核心概念中,而是為了開發方便,擴充出來的語法。當然,Racket 之所以是 Racket,也在於它提供了許多親民的語法,像 for 就是:

(for ([i '(1 2 3)]
      [j "abc"]
      [k #(#t #f)])
  (display (list i j k)))

for 有許多組合語法,詳細可以參考 Racket Reference [1],我們主要說明它的基礎概念,因為我們後續會碰到跟它很像的一個結構。

首先先說明中括號,在 Racket 與 Scheme 裡頭,小括號與中括號是可以互用的,因此在慣例上,我們在定義區域變數時,會使用中括號 []

for 的第一個區塊,是定義在 for 裡要使用的區域變數,這些變數只會存在在 for 迴圈中。定義的方式是 id sequenceid 就是變數名稱,先前已經解釋過。而 sequence 的型態很特別,它只的是在 Racket 語言裡面,具有次序關係的值,例如一個 List、Vector、String。然而,如果只輸入一個數字呢?它也允許你輸入一個正整數 n,然後它會從 0 跳到 n-1。

但是輸入一個數字的方式,其實可讀性並不佳。因此,我們要說明另一個作法:

(for ([i (in-range 10)])
  (display (number->string i)))  ;; 0123456789

如果你要用跳數字的方式,我會建議使用 in-range 的呼叫。

如果要對這些區域變數做些即時性的判斷與處理,可以搭配 for 的 Keyword,如:

(for ([i (in-range 10)]
      #:when (odd? i))
  (display (number->string i)))  ;; 13579

for 還有昨天最後說到的 cadrcdar 之類的特殊語法,其實都屬於 SRFI 的範圍。SRFI 全名是 Scheme Require For Implementation,是 Scheme 語言規格書之外的擴充規格。既然 Requre For Implementation,就代表有許多的實作,而 Racket 語言是把絕大多數的 SRFI 都實作在語言核心中了。


上一篇
(list 'day-07 "組織你的資料 — Racket 基礎資料結構:Pair、List 與 Vector")
下一篇
(let ([day 9]) (display "Let it be! — Racket 的 Local Binding"))
系列文
每天 Racket 3 分鐘17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言