WDL - 变量的数据结构

版本

在之前的介绍中,我们介绍了wdl的整体框架,和任务调度过程中涉及的执行逻辑,同时我们也了解了如何投递一个简单的任务。但是实际肯定不只是hello word。而在面对实际应用过程中,我们往往需要进行一些更复杂的数据处理和业务逻辑的实现。而为了提高整体代码的可理解性,更好的标识一些特定关联数据之间的逻辑关系和数据类型,所以和其他编程语言一样,我们需要用到一些数据结构当然也伴随会使用到一些基础的数据处理。当然同样的和其他编程语法一样,WDL也存在不同版本间的语法差异,因此在使用WDL进行流程撰写时,需要声明版本。本文中若无特殊说明,均是基于 WDL v1.0 版本进行的介绍。

截至目前几个主要的版本分别是:
draft-3
v1.0
v1.1 截止202301,cromwell还不支持该版本。

语法规则

变量

变量类型

基础变量(primitive types )

这个比较好理解,就是基本在所有编程语言中都会涉及的一些基础类型,比如整数型、浮点数、布尔值、字符串。但是除了这四种比较常见的,还有一个相对比较特殊的类型 File类型,这是因为wdl在后期任务执行过程中,会涉及到容器的使用,容器使用过程中,就会涉及到一些数据文件的挂载(宿主机和虚拟机的数据不再可以直接进行访问)。另一方面如果输出类型是File的话,调度系统会对校验文件是否存在。

1
2
3
4
5
Int i = 0                  # An integer value
Float f = 27.3 # A floating point number
Boolean b = true # A boolean true/false
String s = "hello, world" # A string value
File f = "path/to/file" # A file

复合变量(compound types)

符合变量,也会有比较多,比如 Array类似其他编程语言中的属组/序列, Map类似其他编程语言中的字典/哈希,Object就相当于其他语言中的对象(有属性,但是没方法┑( ̄Д  ̄)┍)。
其中比较特殊的类型是Pair,可以理解成只有两个元素的Array,也可以理解成直接两个隐藏key(left,right)的 Map,在NGS领域的话,倒是很适合存储成对的Fastq数据分别对应Fq1和Fq2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# An array of Xs
Array[X] xs = [x1, x2, x3]

# A map from Ps to Ys
Map[P,Y] p_to_y = { p1: y1, p2: y2, p3: y3 }

# Object keys are always `String`s
Object o = { "field1": f1, "field2": f2 }

# A pair of one X and one Y
Pair[X,Y] x_and_y = (x, y)

# 符合变量的套娃使用
Array[Array[X]] xs = [[x1, x2, x3],[y1, y2, y3]]
Arrays数组

数组的定义和访问可以参考类似python的语法,例如:

1
2
3
4
5
6
# An array of Xs
Array[String] a = ["a", "b", "c"]
Array[Int] b = [0,1,2]

# 访问属组中的指定元素
String x = a[2] # 通过索引访问数组特定位置的值,所以索引必需是整数

Map字典

字典的定义和访问可以参考类似python的语法,例如:

1
2
3
4
5
6
7
# A map from Ps to Ys
Map[String, Int] = {"a": 1, "b": 2}
Map[Int, Int] = {1: 10, 2: 11}
Map[P,Y] p_to_y = { p1: y1, p2: y2, p3: y3 } # key 和value的类型可以是声明的自定义结构。

# 访问字典中的指定元素
value = p_to_y[p1] # 通过key访问字典对应key的值,key必须是map中存在的
Object对象

Object的定义和map比较类似,数据的访问可以通过 x.y 的方式访问, 其中 x 必须是一个对象或者是一个 workflow中的 task 。一个Task可以被视为一个对象,而Task的属性就是一个Task中的 output的内容,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  Object o = { "field1": f1, "field2": f2 }     
# Object keys are always `String`s

workflow wf {
input {
Object obj
Object foo
}
call foo {
input: var=obj.attr
# 通过对象 obj 的属性attr,访问获得对象的特定属性值。
}

call foo as foo2 {
input: var=foo.out
# foo.out 通过一个call生成的对象访问对应task执行阶段生成的output中的值
}
}

Pair配对

配对的类型其他语言比较少见,是一个固定格式的,传入两个值的类型,同时可以通过 x.left and x.right 获取左侧和右侧的值,例如

1
2
3
4
Pair[Struct_X,Struct_Y] x_and_y = (x, y)                    
# A pair of one X and one Y
Struct_X x = x_and_y.left # 获取Pair的左侧/第一个值
Struct_Y y = x_and_y.right # 获取Pair的右侧/第二个值

自定义结构(Struct Definition)

struct 是一种类似c的构造,它允许用户创建由先前存在的类型组成的新的复合类型。然后,可以在Task或Workflow定义中使用struct作为声明来代替任何其他常规类型。在许多情况下,结构体替代了Object类型,并允许对其成员进行适当的类型设置。

1
struct SampleData{}

复合类型还可以在结构中使用,以便轻松地将它们封装在单个对象中。

变量的调用

访问Object

wdl本身提供了多种读取文件信息生成Object的接口(read_tsv、read_json、read_map)等。
部分示例如下:
Config_File_software:

1
2
3
pipeline_root	/home/liubo4/Project/aio.wdl/Gdc/aio.NewAnnotation/
java bin/java_v1.8
tabix bin/tabix

要在wdl中提取上述文件信息

1
2
3
4
5
6
7
8
9
10
11
12
workflow wf_echo {
input{
String Config_File_software
Object software = read_map(Config_File_software)
# 方式一:直接通过Config_File_software文件的第一列值作为属性直接提取;
pipeline_root = software.pipeline_root

# 方式二: 通过中间变量提取,这种方式可以高效的处理一些差异化配置需求;
String tmp="pipeline_root"
pipeline_root = software[tmp]
}
}

在一些复杂业务中,可以使用json格式的输入文件,提供丰富的数据结构。包括将json文件内容解析为我们自己定义的 struct 类型

遍历类型

由于WDL语言本身的应用场景,遍历使用的环境并不多,基本上和 scatter 函数绑定使用了,从而实现分析任务的拆解和并行处理。对应的支持遍历的类型主要是两种,分别是Array和Map类型。遍历Array的时候,每次返回一个Array内部的元素类型,和其他编程语言类似,比较好理解。
而遍历Map不是和其他语言一样分别返回key和value。而是会返回一个wdl特有的Pair类型的数据[key,value]对,然后我们可以通过 Pair.left 或 Pair.left 实现key、value获取。参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  # 遍历Map类型数据
Map[String, Int] map
scatter (pair in map) {
String key = pair.left
Int value = pair.right
}
output {
# Automatically gathered from inside the scatter:
Array[String] keys = key
Array[Int] values = value
}

# 遍历属组
Array[String] ArrayList
scatter (Str_info in ArrayList) {
String value = Str_info
}
output {
# Automatically gathered from inside the scatter:
Array[Int] values = value
}
}

变量的可选参数Optional Parameters & Type Constraints

表示该参数是可选的。 用户无需指定参数值即可满足工作流的所有输入。可以实现一些可选参数的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
task test {
input {
String? val # 设置了一个可选参数
}
command {
# python script.py --val=${val}
# 错误的示例,这个示例会执行 python script.py --val= 导致以外错误

python script.py ${"--val=" + val}
# 可选参数不传入时,则运行阶段也不会使用该参数的配置

}
}

+

+ 仅适用于Array类型,它表示数组值必须包含一个或多个元素的约束。
比如我们的下机数据,下机数据文件时不确定的,可能有多对Fastq,可能只有一对Fastq,但是不能没有。

1
Array[Pair[Fq1,Fq2]]+ SequenceData

关于每一种变量的使用,以及 WDL 的更多使用技巧,请参考官方规范文档

表达式(Expressions)

LHS Type Operators RHS Type Result Semantics
Boolean == Boolean Boolean
Boolean != Boolean Boolean
Boolean > Boolean Boolean
Boolean >= Boolean Boolean
Boolean < Boolean Boolean
Boolean <= Boolean Boolean
Boolean `\ \ ` Boolean Boolean
Boolean && Boolean Boolean
File + File File Append file paths
File == File Boolean
File != File Boolean
File + String File
File == String Boolean
File != String Boolean
Float + Float Float
Float - Float Float
Float * Float Float
Float / Float Float
Float % Float Float
Float == Float Boolean
Float != Float Boolean
Float > Float Boolean
Float >= Float Boolean
Float < Float Boolean
Float <= Float Boolean
Float + Int Float
Float - Int Float
Float * Int Float
Float / Int Float
Float % Int Float
Float == Int Boolean
Float != Int Boolean
Float > Int Boolean
Float >= Int Boolean
Float < Int Boolean
Float <= Int Boolean
Float + String String
Int + Float Float
Int - Float Float
Int * Float Float
Int / Float Float
Int % Float Float
Int == Float Boolean
Int != Float Boolean
Int > Float Boolean
Int >= Float Boolean
Int < Float Boolean
Int <= Float Boolean
Int + Int Int
Int - Int Int
Int * Int Int
Int / Int Int Integer division
Int % Int Int Integer division, return remainder
Int == Int Boolean
Int != Int Boolean
Int > Int Boolean
Int >= Int Boolean
Int < Int Boolean
Int <= Int Boolean
Int + String String
String + Float String
String + Int String
String + String String
String == String Boolean
String != String Boolean
String > String Boolean
String >= String Boolean
String < String Boolean
String <= String Boolean
- Float Float
+ Float Float
- Int Int
+ Int Int
! Boolean Boolean

参考

变量赋值时的逻辑判断

有些时候,我们需要根据输入的数据,根据一些逻辑规则完成相应参数的赋值情况,这时候,我们可以使用 if … then … else … 的方式,例如:

1
2
3
4
Int array_length = length(array)
runtime {
memory: if array_length > 100 then "16GB" else "8GB"
}

参考该指标,可以根据数据量动态的给每个任务分配内存。

-------------本文结束感谢您的阅读-------------