Cuneiform_(编程语言)

**Cuneiform** 是用於大規模科學資料分析的開源工作流程語言。
它是促進平行計算的靜態型別的純函數式程式語言。它的特徵是有個全功能的外界函式介面，允許使用者整合來自很多外部程式語言的軟體。Cuneiform 在組織層面上提供了一些設施，如條件分支和通用遞迴，使其具有圖靈完備性。

## 概述

Cuneiform 嘗試拉近在科學工作流程系統如 Apache Taverna、KNIME 或 Galaxy，與大規模資料分析程式模型如 MapReduce 或 Pig Latin 之間的間隙，同時提供函式語言程式設計語言的通用性。

Cuneiform 是用分散式 Erlang 實現的。如果執行在分散式模態下，它匯出一個遵循 POSIX 的分散式檔案系統，如 Gluster 或 Ceph（用 FUSE 整合某個其他檔案系統例如 HDFS）。作為替代選擇，Cuneiform 指令碼可以執行在 HTCondor 或 Apache Hadoop 頂上。

Cuneiform 受到了 Peter Kelly 的工作的影響，他提議函式語言程式設計作為科學工作流程執行的模型。
故而，Cuneiform 不同於其他的基於資料流程編程的工作流程語言，如並列手稿語言 Swift。

## 擴充軟體整合

外部工具和庫（比如 R 或 Python 庫）是通過外界函式介面來整合的。通過它可以組合，比如允許通過 snippet 節點使用外部軟體的 KNIME，或為整合 Java 軟體提供 BeanShell 服務的 Apache Taverna。通過定義採用外界語言的任務，就可能使用一個外部工具或庫的 API。這種方式下，工具可以直接整合而不需要寫包裝器或重新實現工具。

目前支援的外界程式語言有：

計劃增加外界語言 AWK 和 gnuplot。

## 型別系統

Cuneiform 提供簡單的、靜態檢查的型別系統。雖然 Cuneiform 提供列表作為複合資料類型，它省略了傳統的列表存取子（`head` 和 `tail`），以避免在存取空串列時，可能引起的執行時間錯誤的可能性。轉而列表只能通過在其上 `map` 或 `fold`，以全有或全無方式存取。此外，Cuneiform（在組織層面）省略了算術，這排除了除以零的可能性。省略任何的部份定義運算，允許保證執行時間錯誤只能在外界代碼中引發。

### 基礎資料類型

Cuneiform 提供的基礎資料類型有布林值、字串和檔案。這裡的檔案被用來以任意格式在外界函式之間交換資料。

### 記錄和模式匹配

Cuneiform 提供記錄（結構）作為複合資料類型。下面的例子展示定義一個變數 `r`，作為有兩個欄位 `a1` 和 `a2` 的記錄，第一個是字串，而第二個是布林值：

記錄可以要麼通過投影（projection）要麼通過模式匹配來存取。下面的例子從記錄 `r`，提取兩個欄位 `a1` 和 `a2`：

### 列表和列表處理

進一步的，Cuneiform 提供列表作為複合資料類型。下面的例子展示定義一個變數 `xs`，作為一個有三個元素的一個檔案：

列表可以通過 `for` 和 `fold` 算子來處理。這裡的 `for` 算子可以接受多個列表，來逐個元素的處置列表（類似於 Racket 中的 `for/list`、Common Lisp 中的 `mapcar` 或 Erlang 中的 `zipwith`）。

下面的例子展示如何在一個單一的列表上 `map`，結果是一個檔案列表：

下面的例子展示如何 `zip` 兩個列表，結果也是一個檔案列表：

最後，列表可以使用 `fold` 算子來做聚集。下面的例子合計一個列表的元素：

## 並列執行

Cuneiform 是純函數式語言，就是說它不支援可變參照。作為結論，它可以使用獨立子項，將一個程式分解成可並列的各部份。Cuneiform 排程器分布這些部份到做工節點。此外，Cuneiform 採用傳名呼叫求值策略，值只在它對計算結果有貢獻時計算。最後，外界函式應用是記憶化的，用來加速包含以前推導結果的計算。

例如，下列 Cuneiform 程式允許 `f` 和 `g` 的應用平行的執行，而 `h` 是有依賴的，它只在 `f` 和 `g` 二者完成的時候可以開始：

```
let output-of-f : File = f();
let output-of-g : File = g();

h( f = output-of-f, g = output-of-g );
```

下列 Cuneiform 程式通過將函式 `f` 對映到一個三元素列表，建立了三個並列應用：

```
let xs : [File] =
  ['a.txt', 'b.txt', 'c.txt' : File];

for x <- xs do
  f( x = x )
  : File
end;
```

類似的，在記錄 `r` 的構造中，`f` 和 `g` 的應用是獨立的，因此可以並列執行：

## 例子

下面是一個 hello-world 指令碼：

這個指令碼定義了採用 Bash 的一個任務 `greet`，它對其字串實際參數 `person` 預加上 `"Hello "`。這個函式產生具有一個單一字串欄位的記錄 `out`。應用 `greet`，繫結實際參數 `person` 到字串 `"world"`，產生記錄 `<out = "Hello world">`。將這個記錄投影為它的欄位 `out`，求值出字串 `"Hello world"`。

可以通過定義採用 Bash 的一個任務來整合命令列工具：

在這個例子中定義了任務 `samtoolsSort`。它呼叫了工具 SAMtools，處置一個 BAM 格式的輸入檔案，並產生一個排序了也是 BAM 格式的輸出檔案。

## 發行歷史

在 2016 年 4 月，Cuneiform 的實現語言從 Java 切換成了 Erlang，並且在 2018 年 2 月，它的主要發布執行平台從 Apache Hadoop 變更為分散式 Erlang。此外，從 2015 年到 2018 年，HTCondor 曾被作為可替代執行平台來維護。

Cuneiform 的外表語法修訂過兩次，這反映在主版本號上。

### 版本 1

在 2014 年 5 月發布的最初草案中，Cuneiform 密切關聯於 Make，它構造直譯器在執行期間要遍歷的靜態資料依賴圖。與後來版本的主要區別，是缺乏條件、遞迴或靜態型別檢查。區分檔案和字串，是通過同波浪號 `~` 形成一個單一參照的字串。指令碼的查詢表達式，通過 `target` 關鍵字來介入。Bash 是預設外界語言。函式應用必須使用 `apply` 形式來完成，它接受 `task` 作為第一個關鍵字實際參數。一年之後，這種外表語法被一種精簡卻類似的版本所替代。

下面的例子指令碼從一個 FTP 伺服器下載一個參考 genome：

```
declare download-ref-genome;

deftask download-fa( fa : ~path ~id ) *{
    wget $path/$id.fa.gz
    gunzip $id.fa.gz
    mv $id.fa $fa
}*

ref-genome-path = ~'ftp://hgdownload.cse.ucsc.edu/goldenPath/hg19/chromosomes';
ref-genome-id = ~'chr22';

ref-genome = apply(
    task : download-fa
    path : ref-genome-path
    id : ref-genome-id
);

target ref-genome;
```

### 版本 2

Cuneiform 外表語法的第二個草案，首次發表於 2015 年 3 月，在 Cuneiform 的實現語言從 Java 到 Erlang 的遷移期間，持續使用了三年。求值不同於早期方式，直譯器歸約一個表達式，而非遍歷一個靜態圖。在外表語法保持使用這段時期，直譯器被形式化和簡化，這導致了第一個 Cuneiform 的語意規定。語法特徵是有了條件。但是，布林值被編碼為列表，再度利用空串列為布林值 `false`，非空串列為布林值 `true`。遞迴後來作為形式化的副產品而增加。但是，靜態型別檢查，只在後來的版本 3 中介入。

下列指令碼解壓一個壓縮檔案，並把它分解為大小均勻的劃分：

```
deftask unzip( <out( File )> : zip( File ) ) in bash *{
  unzip -d dir $zip
  out=`ls dir | awk '{print "dir/" $0}'`
}*

deftask split( <out( File )> : file( File ) ) in bash *{
  split -l 1024 $file txt
  out=txt*
}*

sotu = "sotu/stateoftheunion1790-2014.txt.zip";
fileLst = split( file: unzip( zip: sotu ) );

fileLst;
```

### 版本 3

Cuneiform 外表語法的目前版本，比較於早期的草案，是嘗試拉近與主流函式語言程式設計語言的間隙。它的特徵是簡單的靜態檢查的型別系統，並在列表之外介入記錄作為第二個複合資料結構類型。布林值獨立為基礎資料類型。

下列指令碼解包一個檔案，結果為一個檔案列表：

```
def untar( tar : File ) -> <fileLst : [File]>
in Bash *{
  tar xf $tar
  fileLst=`tar tf $tar`
}*

let hg38Tar : File =
  'hg38/hg38.tar';

let <fileLst = faLst : [File]> =
  untar( tar = hg38Tar );

faLst;
```

## 參照