Skip to main content

Preparing for static typing

While you can adopt static typing progressively in existing code, many coding patterns are not compatible with static typing. Following best practices and avoiding anti-patterns beforehand makes it easier to migrate to static typing.

Use the strict syntax parser​

Static typing requires the strict syntax parser. It is enabled by default in Nextflow 26.04.

Before you migrate to static typing, use nextflow lint or the language server to check that your code complies with the strict parser.

Avoid deprecated patterns​

When you prepare for the strict parser, address as many deprecation warnings as you can. For example:

Channel.from(1, 2, 3).map { it * 2 }        // deprecated
channel.of(1, 2, 3).map { it -> it * 2 } // best practice

The above example shows how to avoid three deprecated patterns:

  1. Using Channel to access channel factories (use channel instead)
  2. Using the deprecated channel.from factory (use channel.of or channel.fromList instead)
  3. Using the implicit it closure parameter (declare the parameter explicitly instead)

Avoid set and tap operators​

In legacy workflows, you can assign a channel with a standard assignment, the set operator, or the tap operator:

ch = channel.of(1, 2, 3)            // standard assignment
channel.of(10, 20, 30).set { ch } // set
channel.of(10, 20, 30).tap { ch } // tap

Typed workflows do not support set and tap. Use standard assignments instead.

Avoid | and & dataflow operators​

The special operators | and & provide shorthands for writing dataflow logic:

channel.of('Hello', 'Hola', 'Ciao')
| greet
| map { v -> v.toUpperCase() }
| view

Typed workflows do not support these special operators. Use standard assignments and method calls instead:

ch_input = channel.of('Hello', 'Hola', 'Ciao')
ch_greet = greet(ch_input)
ch_greet
.map { v -> v.toUpperCase() }
.view()

Avoid .out for process and workflow outputs​

In legacy workflows, you can use the .out property to access process and workflow outputs:

MY_WORKFLOW()
MY_WORKFLOW.out.foo.view()
MY_WORKFLOW.out.bar.view()

Typed workflows do not support this pattern. Use standard assignments instead:

my_out = MY_WORKFLOW()
my_out.foo.view()
my_out.bar.view()

Use single outputs directly​

In a legacy workflow, you can use the single named output of a process or workflow directly or by name. For example, given a workflow with a single named emit:

workflow MY_WORKFLOW {
main:
ch_foo = channel.of(1, 2, 3)

emit:
foo = ch_foo
}

A legacy workflow can use the output either way:

my_out = MY_WORKFLOW()
my_out.view()
my_out.foo.view()

A typed workflow always receives a single output directly, even if the output is named. Use the output directly instead:

foo = MY_WORKFLOW()
foo.view()
warning

The type checker reports my_out.foo as an error when MY_WORKFLOW is a typed workflow. When MY_WORKFLOW is a legacy workflow, Nextflow reports the error only at runtime because legacy outputs are not typed.

Avoid each input qualifier​

Typed processes do not support the each input qualifier. Use the combine operator to create a single tuple channel instead.

For example:

process align {
input:
path seq
each mode

script:
"""
t_coffee -in $seq -mode $mode > result
"""
}

workflow {
sequences = channel.fromPath('*.fa')
methods = ['regular', 'espresso', 'psicoffee']

align(sequences, methods)
}

Rewrite the script to use the combine operator:

process align {
input:
tuple path(seq), val(mode)

script:
"""
t_coffee -in $seq -mode $mode > result
"""
}

workflow {
sequences = channel.fromPath('*.fa')
methods = ['regular', 'espresso', 'psicoffee']

align(sequences.combine(methods))
}
tip

The each qualifier is discouraged in modern Nextflow code. While it provides a convenient shorthand for combining inputs, it couples the process definition with external workflow logic. Since the introduction of DSL2, Nextflow aims to treat processes as standalone modules that are decoupled from workflow logic.

Avoid legacy operators​

Many operators are not statically typed. You can still use them in typed workflows, but the type checker cannot fully validate your code. You can usually replace these operators with another operator, a standard library function, or both.

For example, the splitCsv operator is not statically typed. Use flatMap and the equivalent Path method instead:

// before
channel.fromPath('samplesheet.csv')
.splitCsv(sep: ',')
.view()

// after
channel.fromPath('samplesheet.csv')
.flatMap { csv -> csv.splitCsv(sep: ',') }
.view()

See Migrating operators for more information.