4. 数据类型入门:Int、Float、Boolean、String、File 与使用场景

数据类型这东西,说实话,在编程里是最基础的砖块。但在 WDL 里,它又有点自己的小脾气。我个人习惯在写工作流的第一步,先把输入输出类型定死,这样后面能少踩很多坑。

WDL 提供了五种基础数据类型,分别是:Int(整形)、Float(浮点型)、Boolean(布尔型)、String(字符串)和 File(文件)。今天我们就一个个把它们理清楚。

4.1 从 Int 和 Float 开始:数字的世界

Int 就是整数,Float 就是带小数点的数字。这俩在 WDL 里的用法跟其他语言差不多,但有个细节你一定要注意:WDL 里的 Float 必须显式地写成带小数点的形式。你写 5 就是 Int,写 5.0 才是 Float。

我刚开始写 WDL 时,习惯性地把 3.14 赋给一个 Int 变量,结果 Cromwell 直接报类型不匹配。看个例子:

workflow TypesDemo {
    Int count = 10
    Float threshold = 0.75

    call MyTask {
        input:
            n = count,
            t = threshold
    }
}

task MyTask {
    input {
        Int n
        Float t
    }
    command {
        echo "count is ~{n}, threshold is ~{t}"
    }
    output {
        String out = read_string(stdout())
    }
}
小提示: 在 WDL 表达式里,Int 和 Float 可以混着做算术运算。比如 Int x = 5 + 3.2 是允许的,结果会变成 Float。但反过来,你把 Float 传给 Int 参数就会失败。

4.2 Boolean:对与错的开关

Boolean 只有两个值:true 和 false。它通常用在条件判断里,控制工作流的执行分支。说白了,就是“如果……就……”的逻辑。

我记得有次在项目里,需要根据样本类型来决定要不要再做二次质控。这个开关就是用 Boolean 来控制的。代码长这样:

workflow QCSelector {
    Boolean run_deep_qc = true

    if (run_deep_qc) {
        call DeepQC {}
    }
    call BasicQC {}
}
要点: Boolean 变量在 WDL 的条件块 if (condition) {} 里直接使用。条件判断不需要用 == true,直接写变量名就行。想取反就用 ! 符号。

4.3 String:文本的容器

String 是万能的,什么文本都能往里放。但在 WDL 里,String 有一个很实用的特性:变量插值。你可以用 ~{变量名} 的写法,把变量塞进字符串里。

举个例子,给样本生成文件名:

workflow Naming {
    String sample_id = "SAMPLE_001"
    String out_prefix = "results/~{sample_id}/aligned"

    call Align {
        input:
            prefix = out_prefix
    }
}

task Align {
    input {
        String prefix
    }
    command {
        echo "output will be ~{prefix}.bam"
    }
}

你看,~{sample_id} 会被替换成实际的值。这比手动拼接字符串方便多了。

注意: 命令行的 ~{} 和 WDL 表达式里的 ${} 是不一样的。在 command {} 块里,必须用 ~{} 来插值。我曾经因为搞混这两个符号,调试了整整一下午。

4.4 File:WDL 的灵魂

File 类型是 WDL 的核心。它代表一个文件路径,可以是本地路径,也可以是云端路径(如 gs://bucket/file.fastq)。Cromwell 会自动处理文件的传输和映射,你只管把 File 传进传出的就好。

但有个坑很多人会掉进去:File 只是一个路径引用,不是文件内容本身。你不能直接对 File 做字符串操作,比如用 read_string() 才能读取内容。

看个实际案例:

workflow FileExample {
    File raw_data = "gs://my-bucket/input.fastq"
    String file_name = basename(raw_data)

    call TrimGalore {
        input:
            fastq = raw_data,
            prefix = file_name
    }
}

task TrimGalore {
    input {
        File fastq
        String prefix
    }
    command {
        trim_galore --fastqc ~{fastq} -o ~{prefix}_trimmed.fastq
    }
    output {
        File trimmed = "~{prefix}_trimmed.fastq"
    }
}
关键认知: WDL 的 File 类型天然支持云存储路径。你可以直接把 GCS、S3、Azure Blob 的路径当作 File 传进去。Cromwell 在运行时会自动替你把文件拉到本地,处理完再上传回去。这件事你基本不用操心。

4.5 一张图理清全部关系

这五种数据类型之间的关系和使用场景,我画了张图,一目了然:

WDL 五种基础数据类型与使用场景 Int 整数 Float 浮点数 Boolean 布尔 String 字符串 File 文件 常见使用场景 · Int:样本数量、循环次数、计数 · Float:阈值、权重、质量分数 · Boolean:流程开关、条件分支 常见使用场景(续) · String:文件名、路径前缀、参数拼接 · File:测序数据、参考基因组、中间结果 · File 是 WDL 工作流的核心传输单位 避坑提醒 File 不等于文件内容,也不等于文件名字符串。需要用 read_string(), basename() 等 WDL 标准库函数来处理。

4.6 类型系统里的几个潜规则

WDL 的类型系统虽然简单,但也有几条潜规则你必须知道。我当年就是没注意这些,被坑过好几次。

规则 说明 示例/反例
隐式类型转换 Int 可以隐式转成 Float,但 Float 不能隐式转成 Int Float f = 5 ✓   Int i = 5.0 ✗
String 传文件 String 可以写路径,但不会触发文件传输机制 File 会自动处理云端拉取,String 不会
Boolean 和 if 块 if 块里不能直接使用 Int 或 String 做条件判断 if (1) {} ✗   必须用 Boolean
File 的不可变性 File 变量一旦赋值,内部路径不能修改 要改路径就新建一个 String 拼接后再赋值
重要: String 和 File 虽然长得像,但本质不同。你把一个 GCS 路径写在 String 里,Cromwell 不会帮你搬文件。只有 File 类型才会触发 Cromwell 的文件定位和传输。我见过有人把输入文件的路径写成 String,结果任务直接跑崩——因为本地根本找不到那个文件。

4.7 实战组合:一个完整的混合类型案例

最后,给你看一个我实际项目中用过的片段,把今天学的五种类型串起来:

workflow AlignmentPipeline {
    File reference_fasta
    File input_fastq
    String sample_name
    Int threads = 8
    Float min_quality = 20.0
    Boolean do_duplicate_marking = true

    call Align {
        input:
            ref = reference_fasta,
            reads = input_fastq,
            sample = sample_name,
            cpu = threads
    }

    if (do_duplicate_marking) {
        call MarkDuplicates {
            input:
                bam = Align.aligned_bam
        }
    }
}

这个工作流里,File、String、Int、Float、Boolean 全用上了。你看,它们各司其职,组合起来就是一条完整的数据处理链条。说白了,类型系统就是给工作流定规矩,让 Cromwell 知道该拿什么数据、做什么处理、产什么结果。

我的习惯: 写 WDL 时,先用 Int 和 String 把逻辑参数定好,再用 File 把数据传输框架搭起来,最后用 Boolean 控制流程分支。按这个顺序来,很少出错。