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:
- Using
Channelto access channel factories (usechannelinstead) - Using the deprecated
channel.fromfactory (usechannel.oforchannel.fromListinstead) - Using the implicit
itclosure 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()
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))
}
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.