Skip to content

Commit c839799

Browse files
authored
Add namespaces to nf-lang (#6176)
--------- Signed-off-by: Ben Sherman <bentshermann@gmail.com>
1 parent 481c773 commit c839799

19 files changed

+439
-619
lines changed

docs/channel.md

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,63 @@
22

33
# Channels
44

5-
Nextflow is based on the dataflow programming model in which processes communicate through channels.
5+
In Nextflow, **channels** are the key data structures that facilitate the dataflow dependencies between each step (i.e. {ref}`process <process-page>`) in a pipeline.
66

7-
A channel has two major properties:
7+
There are two kinds of channels, *queue channels* and *value channels*. Channels are created using *channel factories* and transformed using *channel operators*.
88

9-
1. Sending a message is an *asynchronous* (i.e. non-blocking) operation, which means the sender doesn't have to wait for the receiving process.
10-
2. Receiving a message is a *synchronous* (i.e. blocking) operation, which means the receiving process must wait until a message has arrived.
11-
12-
(channel-types)=
9+
(channel-type-queue)=
1310

14-
## Channel types
11+
## Queue channels
1512

16-
In Nextflow there are two kinds of channels: *queue channels* and *value channels*.
13+
A *queue channel* is a channel that *emits* an asynchronous sequence of values.
1714

18-
(channel-type-queue)=
15+
A queue channel can be created by channel factories (e.g., {ref}`channel.of <channel-of>` and {ref}`channel.fromPath <channel-path>`), operators (e.g., {ref}`operator-map` and {ref}`operator-filter`), and processes (see {ref}`Process outputs <process-output>`).
1916

20-
### Queue channel
17+
The values in a queue channel cannot be accessed directly -- they can only be accessed by passing the channel as input to an operator or process. For example:
2118

22-
A *queue channel* is a non-blocking unidirectional FIFO queue connecting a *producer* process (i.e. outputting a value)
23-
to a consumer process or an operator.
19+
```nextflow
20+
channel.of(1, 2, 3).view { v -> "queue channel emits ${v}" }
21+
```
2422

25-
A queue channel can be created by factory methods ({ref}`channel-of`, {ref}`channel-path`, etc), operators ({ref}`operator-map`, {ref}`operator-flatmap`, etc), and processes (see {ref}`Process outputs <process-output>`).
23+
```console
24+
queue channel emits 1
25+
queue channel emits 2
26+
queue channel emits 3
27+
```
2628

2729
(channel-type-value)=
2830

29-
### Value channel
31+
## Value channels
3032

31-
A *value channel* can be bound (i.e. assigned) with one and only one value, and can be consumed any number of times by
32-
a process or an operator.
33+
A *value channel* is a channel that is *bound* to an asynchronous value.
3334

34-
A value channel can be created with the {ref}`channel-value` factory method or by any operator that produces a single value
35-
({ref}`operator-first`, {ref}`operator-collect`, {ref}`operator-reduce`, etc). Additionally, a process will emit value
36-
channels if it is invoked with all value channels, including simple values which are implicitly wrapped in a value channel.
35+
A value channel can be created with the {ref}`channel.value <channel-value>` factory, certain operators (e.g., {ref}`operator-collect` and {ref}`operator-reduce`), and processes (under {ref}`certain conditions <process-out-singleton>`).
3736

38-
For example:
37+
The value in a value channel cannot be accessed directly -- it can only be accessed by passing the channel as input to an operator or process. For example:
3938

4039
```nextflow
41-
process echo {
42-
input:
43-
val x
44-
45-
output:
46-
path 'x.txt'
47-
48-
script:
49-
"""
50-
echo $x > x.txt
51-
"""
52-
}
53-
54-
workflow {
55-
result = echo(1)
56-
result.view { file -> "Result: ${file}" }
57-
}
40+
channel.value(1).view { v -> "value channel is ${v}" }
5841
```
5942

60-
In the above example, since the `echo` process is invoked with a simple value instead of a channel, the input is implicitly
61-
wrapped in a value channel, and the output is also emitted as a value channel.
62-
63-
See also: {ref}`process-multiple-input-channels`.
43+
```console
44+
value channel is 1
45+
```
6446

6547
## Channel factories
6648

67-
Channel factories are functions that can create channels.
49+
Channel factories are functions that create channels from regular values.
6850

69-
For example, the `channel.of()` factory can be used to create a channel from an arbitrary list of arguments:
51+
The `channel.fromPath()` factory creates a channel from a file name or glob pattern, similar to the `files()` function:
7052

7153
```nextflow
72-
channel.of(1, 2, 3).view()
54+
channel.fromPath('input/*.txt').view()
7355
```
7456

7557
See {ref}`channel-factory` for the full list of channel factories.
7658

7759
## Operators
7860

79-
Channel operators, or _operators_ for short, are functions that consume and produce channels. Because channels are asynchronous, operators are necessary to manipulate the values in a channel, aside from using a process. As a result, operators are useful for implementing the _glue logic_ between processes.
61+
Channel operators, or *operators* for short, are functions that consume and produce channels. Because channels are asynchronous, operators are necessary to manipulate the values in a channel. Operators are particularly useful for implementing glue logic between processes.
8062

8163
Commonly used operators include:
8264

docs/process.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -776,9 +776,9 @@ The process `echo` is executed two times because the `x` channel emits only two
776776
2 and b
777777
```
778778

779-
A different semantic is applied when using a {ref}`value channel <channel-type-value>`. This kind of channel is created by the {ref}`channel.value <channel-value>` factory method or implicitly when a process is invoked with an argument that is not a channel. By definition, a value channel is bound to a single value and it can be read an unlimited number of times without consuming its content. Therefore, when mixing a value channel with one or more (queue) channels, it does not affect the process termination because the underlying value is applied repeatedly.
779+
When a {ref}`value channel <channel-type-value>` is supplied as a process input alongside a queue channel, the process is executed for each value in the queue channel, and the value channel is re-used for each execution.
780780

781-
To better understand this behavior, compare the previous example with the following one:
781+
For example, compare the previous example with the following:
782782

783783
```nextflow
784784
process echo {
@@ -811,7 +811,7 @@ The above example executes the `echo` process three times because `x` is a value
811811
In general, multiple input channels should be used to process *combinations* of different inputs, using the `each` qualifier or value channels. Having multiple queue channels as inputs is equivalent to using the {ref}`operator-merge` operator, which is not recommended as it may lead to {ref}`non-deterministic process inputs <cache-nondeterministic-inputs>`.
812812
:::
813813

814-
See also: {ref}`channel-types`.
814+
See also: {ref}`process-out-singleton`.
815815

816816
(process-output)=
817817

@@ -1160,6 +1160,22 @@ In this example, the process is normally expected to produce an `output.txt` fil
11601160
While this option can be used with any process output, it cannot be applied to individual elements of a [tuple](#output-tuples-tuple) output. The entire tuple must be optional or not optional.
11611161
:::
11621162

1163+
(process-out-singleton)=
1164+
1165+
### Singleton outputs
1166+
1167+
When a process is only supplied with value channels, regular values, or no inputs, it returns outputs as value channels. For example:
1168+
1169+
```{literalinclude} snippets/process-out-singleton.nf
1170+
:language: nextflow
1171+
```
1172+
1173+
In the above example, the `echo` process is invoked with a regular value that is wrapped in a value channel. As a result, `echo` returns a value channel and `greet` is executed three times.
1174+
1175+
If the call to `echo` was changed to `echo( channel.of('hello') )`, the process would instead return a queue channel, and `greet` would be executed only once.
1176+
1177+
See also: {ref}`process-multiple-input-channels`.
1178+
11631179
(process-when)=
11641180

11651181
## When

docs/reference/stdlib-namespaces.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ The `channel` namespace contains the built-in channel factories. See {ref}`chann
119119

120120
(stdlib-namespaces-nextflow)=
121121

122+
## `log`
123+
124+
The `log` namepsace contains functions for logging messages to the console.
125+
126+
`error( message: String )`
127+
: Log an error message to the console.
128+
: This function does not terminate the pipeline -- use the global `error()` function instead.
129+
130+
`info( message: String )`
131+
: Log an info message to the console.
132+
133+
`warn( message: String )`
134+
: Log a warning message to the console.
135+
122136
## `nextflow`
123137

124138
The `nextflow` namespace contains information about the current Nextflow runtime.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
process echo {
2+
input:
3+
val greeting
4+
5+
output:
6+
val greeting
7+
8+
exec:
9+
true
10+
}
11+
12+
process greet {
13+
input:
14+
val greeting
15+
val name
16+
17+
output:
18+
val "$greeting, $name!"
19+
20+
exec:
21+
true
22+
}
23+
24+
workflow {
25+
names = channel.of( 'World', 'Mundo', 'Welt' )
26+
greeting = echo('Hello')
27+
result = greet(greeting, names)
28+
result.view()
29+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Hello, World!
2+
Hello, Mundo!
3+
Hello, Welt!

modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeChecker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private Variable findDslVariable(ClassNode cn, String name, ASTNode node) {
187187
if( isDataflowMethod(mn) && name.equals(mn.getName()) ) {
188188
return wrapMethodAsVariable(mn, name);
189189
}
190-
// built-in variables are methods annotated as @Constant
190+
// built-in constants and namespaces are methods annotated as @Constant
191191
var an = findAnnotation(mn, Constant.class);
192192
if( !an.isPresent() )
193193
continue;

modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private boolean declareAssignedVariable(VariableExpression ve) {
443443
var variable = vsc.findVariableDeclaration(ve.getName(), ve);
444444
if( variable != null ) {
445445
if( isDslVariable(variable) )
446-
vsc.addError("Built-in variable cannot be re-assigned", ve);
446+
vsc.addError("Built-in constant or namespace cannot be re-assigned", ve);
447447
ve.setAccessedVariable(variable);
448448
return false;
449449
}
@@ -492,7 +492,7 @@ else if( node instanceof BinaryExpression be && be.getOperation().getType() == T
492492
vsc.addWarning("Params should be declared at the top-level (i.e. outside the workflow)", target.getName(), target);
493493
// TODO: re-enable after workflow.onComplete bug is fixed
494494
// else
495-
// vsc.addError("Built-in variable cannot be mutated", target);
495+
// vsc.addError("Built-in constant or namespace cannot be mutated", target);
496496
}
497497
else if( variable != null ) {
498498
checkExternalWriteInAsyncClosure(target, variable);
@@ -529,13 +529,20 @@ public void visitMethodCallExpression(MethodCallExpression node) {
529529
declareAssignedVariable(target);
530530
return;
531531
}
532+
if( node.getObjectExpression() instanceof VariableExpression ve )
533+
checkClassNamespaces(ve);
532534
checkMethodCall(node);
533535
var ioc = inOperatorCall;
534536
inOperatorCall = isOperatorCall(node);
535537
super.visitMethodCallExpression(node);
536538
inOperatorCall = ioc;
537539
}
538540

541+
private void checkClassNamespaces(VariableExpression node) {
542+
if( "Channel".equals(node.getName()) )
543+
vsc.addWarning("The use of `Channel` to access channel factories is deprecated -- use `channel` instead", "CHannel", node);
544+
}
545+
539546
private static boolean isOperatorCall(MethodCallExpression node) {
540547
return node.getNodeMetaData(ASTNodeMarker.METHOD_TARGET) instanceof MethodNode mn
541548
&& VariableScopeChecker.isOperator(mn);

modules/nf-lang/src/main/java/nextflow/script/dsl/DslScope.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
/**
1919
* Marker interface for DSL scopes, which define the built-in
20-
* variables and functions for a particular context.
20+
* constants and functions for a particular context.
2121
*
2222
* @author Ben Sherman <bentshermann@gmail.com>
2323
*/
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nextflow.script.dsl;
17+
18+
/**
19+
* Marker interface for namespaces.
20+
*
21+
* @author Ben Sherman <bentshermann@gmail.com>
22+
*/
23+
public interface Namespace {
24+
}

modules/nf-lang/src/main/java/nextflow/script/dsl/ScriptDsl.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,50 @@
2121
import java.util.Map;
2222

2323
import groovy.lang.Closure;
24-
import nextflow.script.types.NextflowMetadata;
25-
import nextflow.script.types.WorkflowMetadata;
24+
import nextflow.script.namespaces.ChannelNamespace;
25+
import nextflow.script.namespaces.LogNamespace;
26+
import nextflow.script.namespaces.NextflowNamespace;
27+
import nextflow.script.namespaces.WorkflowNamespace;
2628

2729
/**
28-
* The built-in constants and functions in a script.
30+
* The built-in namespaces, constants, and functions in a script.
2931
*
3032
* @author Ben Sherman <bentshermann@gmail.com>
3133
*/
3234
public interface ScriptDsl extends DslScope {
3335

36+
// namespaces
37+
38+
@Constant("channel")
39+
@Description("""
40+
The `channel` namespace contains the built-in channel factories.
41+
42+
[Read more](https://nextflow.io/docs/latest/reference/channel.html)
43+
""")
44+
ChannelNamespace getChannel();
45+
46+
@Constant("log")
47+
@Description("""
48+
The `log` namepsace contains functions for logging messages to the console.
49+
50+
[Read more](https://nextflow.io/docs/latest/reference/stdlib-namespaces.html#log)
51+
""")
52+
LogNamespace getLog();
53+
54+
@Constant("nextflow")
55+
@Description("""
56+
The `nextflow` namespace contains information about the current Nextflow runtime.
57+
""")
58+
NextflowNamespace getNextflow();
59+
60+
@Constant("workflow")
61+
@Description("""
62+
The `workflow` namespace contains information about the current workflow run.
63+
""")
64+
WorkflowNamespace getWorkflow();
65+
66+
// constants
67+
3468
@Deprecated
3569
@Constant("baseDir")
3670
@Description("""
@@ -44,24 +78,12 @@ public interface ScriptDsl extends DslScope {
4478
""")
4579
Path getLaunchDir();
4680

47-
@Constant("log")
48-
@Description("""
49-
Logger which can be used to log messages to the console.
50-
""")
51-
Object getLog();
52-
5381
@Constant("moduleDir")
5482
@Description("""
5583
The directory where a module script is located (equivalent to `projectDir` if used in the main script).
5684
""")
5785
Path getModuleDir();
5886

59-
@Constant("nextflow")
60-
@Description("""
61-
Map of Nextflow runtime information.
62-
""")
63-
NextflowMetadata getNextflow();
64-
6587
@Constant("projectDir")
6688
@Description("""
6789
Alias of `workflow.projectDir`.
@@ -80,11 +102,7 @@ The directory where a module script is located (equivalent to `projectDir` if us
80102
""")
81103
Path getWorkDir();
82104

83-
@Constant("workflow")
84-
@Description("""
85-
Map of workflow runtime information.
86-
""")
87-
WorkflowMetadata getWorkflow();
105+
// functions
88106

89107
@Description("""
90108
Create a branch criteria to use with the `branch` operator.

0 commit comments

Comments
 (0)