函式實字

March 4, 2022

OpenSCAD 本身沒有提供排序用的函式,如果你想針對數字排序該怎麼做呢?來實作個〈快速排序法〉吧!

function sort(lt) = 
    let(leng = len(lt))
    leng <= 1 ? lt : 
        let(
            pivot = lt[0],
            before = [for(j = 1; j < leng; j = j + 1) if(lt[j] < pivot) lt[j]],
            after =  [for(j = 1; j < leng; j = j + 1) if(lt[j] >= pivot) lt[j]]
        )
        [each sort(before), pivot, each sort(after)];

// ECHO: [1, 2, 3, 5, 6, 7, 8, 10]
echo(sort([3, 1, 2, 5, 7, 6, 10, 8])); 

嗯?有說要由小而大排序嗎?另外,如果想針對其他類似的資料排序,例如字串長短排序呢?

建立函式值

如果想要實作有彈性的 sort 函式,那麼就不能寫死 lt[j] < pivotlt[j] >= pivot 的部份,最好是可以指定函式,例如:

function sort(lt, compare) = 
    let(leng = len(lt))
    leng <= 1 ? lt : 
        let(
            pivot = lt[0],
            before = [for(j = 1; j < leng; j = j + 1) if(compare(lt[j], pivot) < 0) lt[j]],
            after =  [for(j = 1; j < leng; j = j + 1) if(compare(lt[j], pivot) >= 0) lt[j]]
        )
        [each sort(before, compare), pivot, each sort(after, compare)];

這邊規範 compare 傳回結果若大於 0,表示第一個引數在順序上大於第二個引數,若等於 0,表示順序相同,若小於 0,表示第一個引數在順序上小於第二個引數。

那麼該怎麼傳入函式呢?就目前為止你看到的函式定義,不能作為值傳遞給 sort,如果想要一個可以作為值傳遞的函式,可以使用函式實字(function literal),例如 function(a, b) a - b 是個函式實字,要看仔細,函式實字沒有名稱,參數列後沒有 =,直接就是函式本體。

在程式語言中,實字意謂著你可以用書寫的方式建立一個值,例如,3.14 是個數字實字,會建立一個數值,"abc" 是個字串實字,會建立一個字串值,function(a, b) a - b 是個函式實字的話,表示它會建立一個函式值。

既然是值,就可以指定給變數:

compare = function(a, b) a - b;

或者裝在 list 中成為元素:

compare_functions = [
    function(a, b) a - b, 
    function(a, b) b - a
];

也可以作為引數傳遞:

// ECHO: [10, 8, 7, 6, 5, 3, 2, 1]
echo(
    sort(
        [3, 1, 2, 5, 7, 6, 10, 8], 
        compare = function(a, b) b - a
    )
);

你想針對字串長度排序也可以了:

echo(
    sort(
        ["a", "aa", "a", "aaa"], 
        compare = function(a, b) len(a) - len(b)
    )
);

function vs function?

OpenSCAD 的函式實字,是在 2021.01 版本加入的特性,很可惜地,它使用了與函式定義相同的關鍵字 function,例如,以下是個 ascending 函式定義:

function ascending(a, b) = a - b;

以下建立了一個函式實字,指定給 descending 變數:

descending = function(a, b) b - a;

OpenSCAD 的函式實字,其實就相當於其他語言中的 lambda 運算式(lambda expression)匿名函式(anonymous function)一級函式(first class function)之類的特性,如果能採用 (a, b) -> a - b 或者 lambda(a, b) a - b 之類語法,就不會與函式定義混淆,無論如何,既然函式實字也是使用 function 關鍵字,習慣就好…XD

在〈OpenSCAD CheatSheet〉裡的函式,都是函式定義,函式定義不能作為值傳遞(模組也不行),然而,可以在函式實字中呼叫 OpenSCAD 內建的函式定義,例如,function(lt) len(lt) 是函式實字,就可以傳遞了,像是傳遞給 leng 變數:

leng = function(lt) len(lt); 

函式實字沒有名稱,不過若最後指定給變數,還是可以透過變數名稱來進行遞迴,例如遞迴地計算階乘:

factorial = function(n) n == 1 ? 1 : n * factorial(n - 1);

// ECHO: 120
echo(factorial(5));

既然函式實字建立了函式值,想作為參數預設值也是可以的,例如預設可以比較數字,採昇幕排序:

function sort(lt, compare = function(a, b) a - b) = 
    let(leng = len(lt))
    leng <= 1 ? lt : 
        let(
            pivot = lt[0],
            before = [for(j = 1; j < leng; j = j + 1) if(compare(lt[j], pivot) < 0) lt[j]],
            after =  [for(j = 1; j < leng; j = j + 1) if(compare(lt[j], pivot) >= 0) lt[j]]
        )
        [each sort(before, compare), pivot, each sort(after, compare)];

// ECHO: [1, 2, 3, 5, 6, 7, 8, 10]
echo(sort([3, 1, 2, 5, 7, 6, 10, 8])); 

分享到 LinkedIn 分享到 Facebook 分享到 Twitter