---
title: Semantics
description: Reference for the semantics of Nextflow language constructs.
---

# Semantics

The *semantics* of the Nextflow language are the meaning and behavior of each language construct. This page is a companion to the [Syntax][syntax-page] reference, which describes the *grammar* of the language (how constructs are written). See the [Scripts][script-page] page for a practical introduction to the language.

## Values

Every expression in Nextflow produces a *value*, and every value has a [type](#types) that determines the operations it supports. This section describes the kinds of values in the language and how they behave. Each kind of value links to its type reference, which describes the full set of available operations. See [Syntax][syntax-page] for the grammar of each literal.

### Booleans

A boolean is either `true` or `false`. Booleans are produced by the [relational](#relational-operators) and [logical](#logical-operators) operators and consumed by constructs that test a condition, such as [if/else](#ifelse) and [assertions](#assertions).

Any value can be coerced to a boolean based on its *truthiness*. A value that coerces to `true` is *truthy*, and a value that coerces to `false` is *falsy*.

The truthiness of a value depends on its type:

| Type            | Falsy values                      | Truthy values            |
| --------------- | --------------------------------- | ------------------------ |
| Boolean         | `false`                           | `true`                   |
| Number          | zero (`0`, `0.0`)                 | any non-zero number      |
| String          | empty string (`''`)               | any non-empty string     |
| Collection      | empty collection (`[]`)           | any non-empty collection |
| Map             | empty map (`[:]`)                 | any non-empty map        |
| Everything else | `null`                            | any non-null value       |

For example:

```nextflow
assert true
assert 42
assert 'hello'
assert [1, 2, 3]
assert [key: 'value']

assert !false
assert !0
assert !''
assert ![]
assert ![:]
assert !null
```

### Numbers

A number is either an integer or a floating-point number.

Numbers support the standard [arithmetic operators](#arithmetic-operators). Arithmetic between two integers produces an integer, with one exception: division always produces a floating-point result. Use the `intdiv` method for integer division.

```nextflow
assert 2 + 3 == 5
assert 7 / 2 == 3.5         // division produces a float
assert 7.intdiv(2) == 3     // integer division
```

When an operation mixes an integer and a floating-point operand, the integer is coerced to floating-point.

See [Integer][stdlib-types-integer] and [Float][stdlib-types-float] for the available operations.

### Strings

A string is a sequence of characters, written with single or double quotes (see [String](./syntax#string) for the grammar). Strings are immutable, and can be concatenated with the `+` operator:

```nextflow
def greeting = 'Hello, ' + 'world!'
```

A double-quoted string supports *interpolation* -- embedded expressions are evaluated and their results inserted into the string. A single-quoted string is a literal and is never interpolated:

```nextflow
def name = 'world'
println "Hello, ${name}!"   // -> Hello, world!
println 'Hello, ${name}!'   // -> Hello, ${name}!
```

An interpolated expression is written as `${expression}`. The curly braces can be omitted for a simple variable or property reference:

```nextflow
def sample = [id: 'A', count: 42]
println "Sample $sample.id has $sample.count reads"
// -> Sample A has 42 reads
```

Each interpolated expression is evaluated when the string is created, and its result is converted to a string using the value's `toString()` method. Interpolation is *eager* -- the expression is evaluated immediately, not deferred:

```nextflow
def x = 1
def s = "x is ${x}"
x = 2
println s   // -> x is 1
```

Single-quoted strings are not interpolated:

```nextflow
println 'Hello, ${name}!'
// -> Hello, ${name}!
```

See [String][stdlib-types-string] for the available operations.

### Null

The special value `null` represents the absence of a value. Accessing a property or calling a method on `null` raises a *null reference* error. Avoid `null` where possible. The [safe navigation operator](#navigation-operators) can be used to access a property that may be `null`.

### Collections

A collection is a group of values. Nextflow provides three kinds of collection, which differ in whether they preserve the order of their elements and whether they allow duplicates:

| Collection | Ordered | Duplicates |
| ---------- | ------- | ---------- |
| List       | yes     | yes        |
| Set        | no      | no         |
| Bag        | no      | yes        |

A *list* is the most commonly used collection. It is written with square brackets, and its elements are accessed by index, starting at zero. Lists are mutable -- elements can be added, removed, or replaced:

```nextflow
def numbers = [1, 2, 3]
assert numbers[0] == 1
```

A *set* is an unordered collection that cannot contain duplicates. It is typically created from a list with the `toSet()` method:

```nextflow
def unique = [1, 2, 2, 3].toSet()
assert unique.size() == 3
```

A *bag* is an unordered collection that allows duplicates. It is used where the order of elements is not significant.

All collections can be iterated with [higher-order functions](#iteration) and tested with the [membership operators](#membership-operators). The `+` operator returns a *new* collection containing the elements of both operands, rather than modifying either one:

```nextflow
assert [1, 2] + [3, 4] == [1, 2, 3, 4]
```

See [List\<E\>][stdlib-types-list], [Set\<E\>][stdlib-types-set], and [Bag\<E\>][stdlib-types-bag] for the available operations.

### Maps

A map is a collection of key-value pairs, written with square brackets:

```nextflow
def scores = [alice: 100, bob: 92]
```

Values are accessed by key, using either the index operator or a property expression:

```nextflow
assert scores['alice'] == 100
assert scores.alice == 100
```

The `+` operator returns a *new* map containing the entries of both operands, with the right operand taking precedence on conflicting keys:

```nextflow
assert [a: 1, b: 2] + [b: 3] == [a: 1, b: 3]
```

See [Map\<K,V\>][stdlib-types-map] for the available operations.

### Records

<AddedInVersion version="26.04" />

A record holds a set of named fields, where each field can have its own type. Records are created with the `record()` function:

```nextflow
def person = record(name: 'Alice', age: 42)
```

Fields are accessed by name:

```nextflow
assert person.name == 'Alice'
```

Records are immutable -- once a record is created, it cannot be modified. Combine two records using the `+` operator to produce a new record:

```nextflow
def older = person + record(age: 43)
// -> record(name: 'Alice', age: 43)
```

See [Record][stdlib-types-record] for the available operations.

### Tuples

A tuple is a fixed sequence of values, which may have different types. Tuples are created with the `tuple()` function:

```nextflow
def pair = tuple('sample_1', file('sample_1.fastq'))
```

Tuple elements are accessed by index:

```nextflow
assert pair[0] == 'sample_1'
```

Tuples are immutable -- once a tuple is created, its elements cannot be modified.

A tuple can be *destructured* into multiple variables, both in [assignments](#assignments) and in [closure](#closures) parameters.

See [Tuple][stdlib-types-tuple] for the available operations.

### Closures

A closure is an anonymous function that is itself a value. A closure consists of a parameter list and a body, enclosed in curly braces:

```nextflow
def square = { v -> v * v }
assert square.call(9) == 81
```

Closures have the same parameter and return semantics as [function](#functions).

A closure can access variables from the enclosing scope, and can declare local variables that exist only for the duration of each invocation:

```nextflow
def factor = 2
assert [1, 2, 3].collect { v -> factor * v } == [2, 4, 6]
```

When a closure receives a single [tuple](#tuples) argument, the parameter list can destructure the tuple. The number of parameters must match the number of tuple elements:

```nextflow
def samples = [
    tuple('sample_1', 100),
    tuple('sample_2', 200),
]

samples.each { id, count ->
    println "${id}: ${count}"
}
```

Within a nested closure, `return` exits the nearest enclosing closure, not the outer function.

The primary use of a closure is as an argument to a *higher-order function* -- a function that takes a closure and applies it, such as the [iteration](#iteration) methods of a collection or the [operators][operator-page] of a channel.

## Operators

An *operator* is a symbol that performs an operation on one or two operands. The full set of operators and their grammar is listed in the [Syntax][syntax-operators] reference. This section describes their semantics, grouped by category.

:::note
The operators described here are language operators, which apply to ordinary values. They are different from *channel operators*, which are functions for manipulating [channels](#workflows). See [Operators][operator-page] for channel operators.
:::

### Arithmetic operators

Arithmetic operators perform numeric computation:

| Operator | Operation                  | Example          |
| -------- | -------------------------- | ---------------- |
| `+`      | addition                   | `2 + 3` -> `5`   |
| `-`      | subtraction                | `5 - 2` -> `3`   |
| `*`      | multiplication             | `2 * 3` -> `6`   |
| `/`      | division                   | `7 / 2` -> `3.5` |
| `%`      | remainder (modulo)         | `7 % 3` -> `1`   |
| `**`     | power (exponentiation)     | `2 ** 8` -> `256`|

Division of two integers produces a floating-point result (`7 / 2` is `3.5`, not `3`). To perform integer division, use the `intdiv` method: `7.intdiv(2)` is `3`.

Some arithmetic operators are *overloaded* for non-numeric types. In particular, `+` concatenates strings, lists, and maps:

```nextflow
assert 'foo' + 'bar' == 'foobar'
assert [1, 2] + [3, 4] == [1, 2, 3, 4]
assert [a: 1] + [b: 2] == [a: 1, b: 2]
```

### Relational operators

Relational operators compare two values and produce a boolean:

| Operator      | Operation                       |
| ------------- | ------------------------------- |
| `==`          | equal to                        |
| `!=`          | not equal to                    |
| `<`           | less than                       |
| `>`           | greater than                    |
| `<=`          | less than or equal to           |
| `>=`          | greater than or equal to        |
| `<=>`         | three-way comparison (spaceship)|

In Nextflow, `==` tests *value equality*, not object identity -- two distinct objects with the same contents are equal:

```nextflow
assert [1, 2, 3] == [1, 2, 3]
assert 'hello' == 'hello'
```

Ordering operators (`<`, `>`, `<=`, `>=`) compare values according to their natural order: numbers numerically and strings alphabetically. The spaceship operator `<=>` returns `-1`, `0`, or `1` depending on whether the left operand is less than, equal to, or greater than the right operand, and is useful for defining custom sort comparators:

```nextflow
def items = ['banana', 'apple', 'cherry']
items.toSorted { a, b -> a <=> b }
// -> ['apple', 'banana', 'cherry']
```

### Logical operators

Logical operators combine boolean values. Their operands are coerced based on [truthiness](#booleans):

| Operator | Operation   |
| -------- | ----------- |
| `&&`     | logical AND |
| `\|\|`   | logical OR  |
| `!`      | logical NOT |

The `&&` and `||` operators *short-circuit*: the right operand is evaluated only if necessary. For `&&`, the right operand is evaluated only if the left is truthy. For `||`, it is evaluated only if the left is falsy. This allows the right operand to safely depend on the left:

```nextflow
if( reads != null && reads.size() > 0 )
    // reads.size() is only evaluated if reads is not null
```

### Bitwise operators

Bitwise operators operate on the individual bits of integer values:

| Operator | Operation              |
| -------- | ---------------------- |
| `&`      | bitwise AND            |
| `\|`     | bitwise OR             |
| `^`      | bitwise XOR            |
| `~`      | bitwise NOT            |
| `<<`     | left shift             |
| `>>`     | right shift            |
| `>>>`    | unsigned right shift   |

```nextflow
assert (0b1100 & 0b1010) == 0b1000   // -> 8
assert (0b1100 | 0b1010) == 0b1110   // -> 14
assert (1 << 4) == 16
```

:::note
The `>>` operator is also used in [typed processes](./syntax#process-typed) to emit values to a topic, and in [workflow outputs](../workflow#publishing-files) to publish files.
:::

### Conditional operators

Conditional operators select a value based on a condition.

The *ternary operator* `cond ? a : b` evaluates `cond` (using [truthiness](#booleans)) and returns `a` if it is truthy, otherwise `b`:

```nextflow
def label = sample.single_end ? 'single' : 'paired'
```

The *Elvis operator* `a ?: b` is a shorthand that returns `a` if it is truthy, otherwise `b`. It is commonly used to supply a default value:

```nextflow
def threads = params.threads ?: 1
```

In both cases, only the selected branch is evaluated.

### Navigation operators

Navigation operators access properties and methods of a value.

The *dot operator* `a.b` accesses the property `b` (or calls the method `b()`) of `a`:

```nextflow
myFile.name
myFile.getName()
```

The *safe dot operator* `a?.b` returns `null` if `a` is `null`, instead of raising a null reference error. This avoids the need for explicit null checks:

```nextflow
def name = sample?.reads?.name
// null if sample or sample.reads is null
```

The *spread dot operator* `a*.b` applies the property access or method call to each element of a collection, returning a list of the results:

```nextflow
def files = [file('a.txt'), file('b.txt')]
files*.name
// -> ['a.txt', 'b.txt']
// equivalent to: files.collect { f -> f.name }
```

### Range operators

Range operators create a [list](#collections) of consecutive values. A range can be formed from any ordered type, such as integers or characters:

| Operator | Operation              |
| -------- | ---------------------- |
| `..`     | inclusive range        |
| `..<`    | right-exclusive range  |

```nextflow
assert (1..5) == [1, 2, 3, 4, 5]
assert (1..<5) == [1, 2, 3, 4]
assert ('a'..'f') == ['a', 'b', 'c', 'd', 'e', 'f']
```

### Membership operators

Membership operators test whether a [collection](#collections) contains a value:

| Operator | Operation           |
| -------- | ------------------- |
| `in`     | membership          |
| `!in`    | negated membership  |

```nextflow
assert 2 in [1, 2, 3]
assert 4 !in [1, 2, 3]
```

For a [map](#maps), the `in` operator tests for the presence of a key.

### Type operators

Type operators convert or test the type of a value:

| Operator      | Operation             |
| ------------- | --------------------- |
| `as`          | type cast             |
| `instanceof`  | type test             |
| `!instanceof` | negated type test     |

The `as` operator casts a value to a different type (see [Types](#types)). The `instanceof` operator tests whether a value is an instance of a given type:

```nextflow
assert ('42' as Integer) == 42
assert 'foo' instanceof String
assert 42 !instanceof String
```

### Regex operators

Regex operators match a string against a regular expression:

| Operator | Operation    |
| -------- | ------------ |
| `=~`     | regex find   |
| `==~`    | regex match  |

The `=~` operator is truthy if the pattern occurs anywhere in the string, whereas `==~` is true only if the entire string matches the pattern:

```nextflow
assert 'hello world' =~ /world/         // find: pattern occurs in the string
assert !('hello world' ==~ /world/)     // match: pattern must match the whole string
assert '2.7.3' ==~ /\d+\.\d+\.\d+/
```

### Operator precedence

When an expression contains multiple operators, *precedence* determines the order in which they are applied. Operators with higher precedence are applied before those with lower precedence. For example, `*` has higher precedence than `+`, so `1 + 2 * 3` is `7`, not `9`.

The full precedence order, from highest to lowest, is listed in the [Syntax][syntax-precedence] reference. The most important rules to remember are:

- Property access, method calls, and index expressions bind most tightly.
- Unary operators (`!`, `~`, unary `-`) bind more tightly than binary arithmetic.
- Arithmetic binds more tightly than comparison, which binds more tightly than the logical operators.
- The conditional operators (`?:`) have the lowest precedence.

When in doubt, use parentheses to make the intended grouping explicit:

```nextflow
(1 + 2) * 3   // -> 9
1 + 2 * 3     // -> 7
```

## Statements

A *statement* performs an action, such as declaring a variable, branching on a condition, or raising an error. Statements are executed in order, top to bottom.

### Variable declarations

A variable is declared with the `def` keyword and an initial value:

```nextflow
def x = 42
```

Multiple variables can be declared in a single statement by *destructuring* a [tuple](#tuples). The number of variables must match the number of elements:

```nextflow
def (id, reads) = tuple('sample_1', file('sample_1.fastq'))
// id    -> 'sample_1'
// reads -> file('sample_1.fastq')
```

Each variable has a *scope*, which is the region of code in which it can be used:

- Variables declared in a function, along with the function's parameters, exist for the duration of each function call. The same applies to closures.

- Workflow inputs exist for the entire workflow body. Variables declared in the `main:` section exist for the `main`, `emit`, and `publish` sections. Named outputs are not variable declarations and therefore have no scope.

- Process input variables exist for the entire process body. Variables declared in the process `script`, `exec`, and `stub` sections exist only within their respective section, with one exception -- variables declared without the `def` keyword also exist in the `output` section.

- Variables declared in an if or else branch exist only within that branch:

  ```nextflow
  if( true )
      def x = 'hello'
  println x           // error: `x` is undefined

  // solution: declare `x` outside of the if branch
  def x
  if( true )
      x = 'hello'
  println x
  ```

A variable cannot be declared with the same name as another variable in the same scope or an enclosing scope:

```nextflow
def clash(x) {
    def x           // error: `x` is already declared
    if( true )
        def x       // error: `x` is already declared
}
```

### Assignments

An assignment updates the value of a *target*, which can be a variable, an index expression, or a property expression:

```nextflow
x = 42
list[0] = 'first'
map.key = 'value'
```

Multiple targets can be assigned in a single statement by *destructuring* a [tuple](#tuples), in the same way as a [declaration](#variable-declarations) but without `def`:

```nextflow
(id, reads) = tuple('sample_1', file('sample_1.fastq'))
```

A *compound assignment* combines a binary operation with the assignment. For example, `x += y` is equivalent to `x = x + y` -- the target is read, combined with the source, and the result assigned back:

```nextflow
def x = 1
x += 2
assert x == 3
```

Because the underlying operators are [overloaded](#arithmetic-operators), compound assignment also works for strings and collections:

```nextflow
def items = [1, 2]
items += [3]
assert items == [1, 2, 3]
```

The following compound assignment operators are available, each corresponding to a [binary operator](#operators): `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `>>>=`.

The *elvis assignment* `?=` is a special case: `x ?= y` is equivalent to `x = x ?: y`, assigning `y` only if `x` is currently [falsy](#booleans). It is useful for setting a default value:

```nextflow
def x = null
x ?= 42
assert x == 42
```

### if/else

An [if/else statement](./syntax#ifelse) conditionally executes one of two branches based on a boolean condition. The condition does not need to be a boolean value -- it is coerced to a boolean using [truthiness](#booleans):

```nextflow
def files = file('*.txt')
if( files ) {
    println "Found ${files.size()} files"
}
else {
    println 'No files found'
}
```

An if/else statement is a *statement*, not an *expression* -- it does not produce a value. To select between two values based on a condition, use a [conditional expression](#conditional-operators) instead:

```nextflow
def message = files ? "Found ${files.size()} files" : 'No files found'
```

### Iteration

Nextflow does not support imperative loop statements such as `for` and `while`. Instead, iteration is expressed declaratively using *higher-order functions* -- methods that take a [closure](#closures) and apply it to each element of a collection.

For example, instead of a `for` loop:

```groovy
// Groovy (not supported in Nextflow)
def result = []
for( x in [1, 2, 3] ) {
    result.add(x * x)
}
```

Use the `collect` method to transform each element:

```nextflow
def result = [1, 2, 3].collect { x -> x * x }
// -> [1, 4, 9]
```

Higher-order functions make the *intent* of the iteration explicit. Common operations include:

- `each`: perform a side effect for each element
- `collect`: transform each element into a new value (i.e. *map*)
- `find` / `findAll`: select the first / all elements that satisfy a condition (i.e. *filter*)
- `inject`: combine all elements into a single value (i.e. *reduce* or *fold*)
- `any` / `every`: test whether any / all elements satisfy a condition

```nextflow
def numbers = [1, 2, 3, 4, 5]

numbers.each { v -> println v }             // print each number
numbers.collect { v -> v * 2 }              // -> [2, 4, 6, 8, 10]
numbers.findAll { v -> v % 2 == 0 }         // -> [2, 4]
numbers.inject(0) { acc, v -> acc + v }     // -> 15
numbers.every { v -> v > 0 }                // -> true
```

A higher-order function returns a value rather than mutating external state, which makes it easier to reason about and avoids common errors. For example, `inject` accumulates a result without an external accumulator variable:

```nextflow
// instead of mutating an external variable
def total = 0
[1, 2, 3].each { v -> total += v }

// accumulate the result directly
def total = [1, 2, 3].inject(0) { acc, v -> acc + v }
```

See [Iterable\<E\>][stdlib-types-iterable] for the full set of higher-order functions for collections such as lists and sets. To process the values in a [channel](#workflows), use [operators][operator-page] such as `map`, `filter`, and `reduce` instead.

### Assertions

An [assert statement](./syntax#assert) evaluates a boolean condition and raises an error if the condition is false. Like an if condition, the asserted expression is coerced to a boolean using [truthiness](#booleans):

```nextflow
assert params.input != null : 'The --input parameter is required'
```

If the condition is false, an error is raised. The optional message after the colon is included in the error. If no message is given, Nextflow reports the values of the sub-expressions in the condition:

```nextflow
assert 2 + 2 == 5
// Assertion failed:
// assert 2 + 2 == 5
//          |   |
//          4   false
```

Assertions are intended for verifying invariants -- conditions that should always hold if the code is correct. They are commonly used in tests and for sanity checks. To validate user inputs or report expected error conditions, use the [`error`](#error-handling) function instead, which produces a cleaner message without the assertion trace.

### Error handling

When an error occurs at runtime, an *exception* is raised. An exception propagates up through the call stack until it is caught by an enclosing [try/catch statement](./syntax#trycatch), or else it reaches the Nextflow runtime and terminates the pipeline.

The recommended way to raise an error in pipeline code is the [`error`][stdlib-error] function, which stops the pipeline with a clear error message:

```nextflow
if( params.aligner !in ['bowtie2', 'bwamem'] )
    error "Unsupported aligner: ${params.aligner}"
```

The lower-level [`throw`](./syntax#throw) statement can be used to raise a specific exception type, but `error` should be preferred for reporting pipeline errors, as it produces a cleaner message and does not require constructing an exception:

```nextflow
// preferred
error 'Something failed!'

// equivalent, but more verbose
throw new Exception('Something failed!')
```

A try/catch statement is appropriate when an error is *expected* and can be handled in pipeline logic -- for example, falling back to a default value when an optional file cannot be read:

```nextflow
def config
try {
    config = file('custom.config').text
}
catch( e: IOException ) {
    log.warn 'Could not read custom.config, using defaults'
    config = ''
}
```

A catch clause matches an exception if the raised exception is an instance of the declared type. If no catch clause matches, the exception propagates to the next enclosing try/catch statement, or terminates the pipeline. Use try/catch only for errors you intend to recover from -- otherwise, let the error propagate so the failure is visible.

:::note
The `error` function and try/catch handle errors in *pipeline code* (functions, closures, the workflow body). They are distinct from two other mechanisms that operate at different levels:

- The [`errorStrategy`][process-error-strategy] directive determines what happens when a process *task* fails (e.g. a command exits with a non-zero status): the task can be retried, ignored, or terminate the pipeline. This is the appropriate mechanism for handling failures of the tools that a process executes.

- The [`onError`][workflow-handlers] workflow handler runs when the pipeline as a whole fails, allowing for cleanup or notification.
:::

## Functions

A [function](./syntax#function) is a named, reusable block of code that takes zero or more parameters and may return a value:

```nextflow
def sayHello(name: String) {
    println "Hello, ${name}!"
}

def add(a: Integer, b: Integer) -> Integer {
    a + b
}
```

### Parameters

Arguments are passed *by reference* -- the function receives a reference to the same object, not a copy. Mutating a mutable argument (such as a list or map) inside a function affects the caller's object:

```nextflow
def addItem(list, item) {
    list.add(item)
}

def items = [1, 2]
addItem(items, 3)
println items   // -> [1, 2, 3]
```

To avoid this, create a copy before mutating, or use operations that return a new value (e.g. `list + [item]` instead of `list.add(item)`).

*Reassigning* a parameter, on the other hand, does not affect the caller. The parameter is a separate reference to the same object, so assigning a new value to it changes only the function's local reference:

```nextflow
def replace(list) {
    list = [4, 5, 6]    // reassigns the local reference only
}

def items = [1, 2, 3]
replace(items)
println items   // -> [1, 2, 3]
```

### Default parameter values

A function parameter can declare a *default value*, which is used when the caller does not supply an argument for that parameter:

```nextflow
def greet(name, greeting = 'Hello') {
    "${greeting}, ${name}!"
}

greet('world')              // -> Hello, world!
greet('world', 'Hi')        // -> Hi, world!
```

Parameters with default values must come after parameters without defaults. A default value can be any expression, and is evaluated each time the function is called without that argument.

### Named arguments

A [function call](./syntax#function-call) may use *named arguments* -- arguments specified by name rather than position. Named arguments are collected into a map and passed as the first positional argument to the function:

```nextflow
file('hello.txt', checkIfExists: true)
```

is equivalent to:

```nextflow
file([checkIfExists: true], 'hello.txt')
```

Named arguments are commonly used by built-in functions and process directives to specify optional settings:

```nextflow
// process directive with named options
accelerator 4, type: 'nvidia-tesla-v100'

// channel factory with named options
channel.fromPath('*.txt', checkIfExists: true)
```

Because named arguments are collected into a single map, their order does not matter, and they can be freely mixed with positional arguments. A function that accepts named arguments must declare a `Map` as its first parameter.

### Returns

A function returns the value of its last evaluated expression, or the value of an explicit [`return`](./syntax#return) statement:

```nextflow
def add(a, b) {
    a + b       // implicit return
}

def addExplicit(a, b) {
    return a + b
}
```

If a function has multiple return statements (including implicit returns), they must either all return a value or all return nothing. When a value is returned, every conditional branch must return a value:

```nextflow
def isEven1(n) {
    if( n % 2 == 1 )
        return          // error: a return value is required here
    return true
}

def isEven2(n) {
    if( n % 2 == 0 )
        return true
                        // error: a return value is required here
}
```

If the last statement of a function is not a return or expression statement, it is equivalent to a bare `return` with no value.

## Processes

A process defines a task that runs in an isolated environment. A process is *called* like a function from a workflow, but instead of running immediately, each call adds a node to the [dataflow graph](#workflows). Nextflow creates a separate *task* for each input (or combination of inputs) and executes these tasks asynchronously as their inputs become available, potentially in parallel and on distributed compute.

See [Processes][process-page] for a full description of how to define and use processes.

## Workflows

A Nextflow workflow describes a *dataflow* -- a graph of [processes][process-page] and [operators][operator-page] connected by channels. See [Processes][process-page] and [Workflows][workflow-page] for a full description of how to define and compose them.

### Definition vs execution

A workflow body is not executed step by step like an ordinary function. Instead, it is evaluated *once*, up front, to *construct* the dataflow graph. The statements in a workflow body declare how processes, operators, and channels are wired together. The actual computation happens *afterward*, asynchronously, as data flows through the graph.

In this sense, a workflow definition behaves like a *compile-time macro*: it runs once to build the dataflow graph, and the operations it describes (process tasks, operator logic) execute later as data becomes available.

Consider:

```nextflow
workflow {
    ch_input = channel.of(1, 2, 3)
    ch_result = ch_input.map { v -> expensive(v) }
    ch_result.view()
}
```

When the workflow body runs:

1. `channel.of(1, 2, 3)` creates a channel.
2. `.map { ... }` connects a `map` operator to that channel and returns a new channel. The closure is *not* called yet -- it is registered to run later, once per value.
3. `.view()` connects a `view` operator.

Only after the entire graph is constructed does Nextflow begin executing it: the channel emits its values, the `map` closure runs for each value, and `view` prints each result.

This has several consequences:

- **Channel contents cannot be accessed directly.** Because the workflow body runs before any data flows, you cannot inspect the values in a channel from within the workflow body. You must use an operator or process to act on each value:

  ```nextflow
  // does NOT work -- the channel has no values yet when this runs
  def first = ch_input[0]

  // correct -- the closure runs later, for each value
  ch_input.map { v -> transform(v) }
  ```

- **Operator closures run asynchronously.** The closure passed to an operator such as `map` or `filter` is invoked later, once per channel value, and possibly out of order. It should not depend on mutable state shared with other closures.

- **The order of statements describes connections, not execution order.** Two independent branches of a workflow are constructed in the order written, but their tasks may execute concurrently.

This is the fundamental difference between the *workflow definition* (evaluated synchronously to build the graph) and the *workflow execution* (runs the graph asynchronously). See [Dataflow][workflow-dataflow] for more information.

## Scripts

A script is a single Nextflow source file. It consists of [script declarations](./syntax#script-declarations) -- such as workflows, processes, functions, and types -- and may use declarations from other scripts.

### Execution

When a script is executed, its *entry workflow* -- the workflow with no name -- serves as the entrypoint. A script that contains only [statements](#statements), with no declarations, is treated as the body of an implicit entry workflow. To be executable, a script must either be a code snippet or define an entry workflow.

### Inclusion

A script can use the definitions of another script through an [include declaration](./syntax#include):

```nextflow
include { hallo as sayHello } from './some/module'
```

The include source should refer to either a local path (e.g. `./module.nf`), a registry module (e.g. `nf-core/fastq`), or a plugin (e.g. `plugin/nf-hello`).

The following definitions can be included from a script:

- Functions
- Processes
- Named workflows
- *New in 26.04:* Record types
- *New in 26.04:* Enum types

The entry workflow of an included script is ignored. This allows the same script to be executed directly or included by another script.

## Types

Every value in Nextflow has a *type*, which determines the set of operations that can be performed on it. The available types are described in [Values](#values).

Nextflow supports two styles of typing:

- **Dynamic typing** (the default): variables and parameters do not declare a type, and the type of each value is determined at runtime. This is the traditional behavior.

- **Static typing** (opt-in): variables, parameters, process inputs/outputs, and workflow inputs/outputs can declare a type, which is checked at compile time. Static typing is enabled with the `nextflow.enable.types` feature flag.

A type annotation is written after the name, separated by a colon:

```nextflow
def count: Integer = 0
def name: String = 'sample'
def reads: List<Path> = []
```

### Nullable types

A type annotation can be suffixed with `?` to indicate that the value may be `null`:

```nextflow
def x: String? = null
```

### Generic type arguments

Some types are *generic* -- they are parameterized by one or more *type arguments* that specify the types of their contents. A type argument is written in angle brackets after the type name:

- `List<E>` is a list whose elements have type `E`
- `Channel<E>` is a channel whose values have type `E`
- `Map<K,V>` is a map whose keys have type `K` and values have type `V`

For example:

```nextflow
// a list of strings
def sequences: List<String> = ['ATCG', 'GCTA']

// a map from string keys to integer values
def counts: Map<String,Integer> = [sample1: 1000, sample2: 1500]

// a channel of file paths
def ch_bams: Channel<Path> = channel.fromPath('*.bam')
```

Generic type arguments allow the type checker to verify that the contents of a collection or channel are used consistently -- for example, that a list of `Path` is not mistakenly treated as a list of `String`.

### Record types

<AddedInVersion version="26.04" />

A record type is a user-defined type that specifies a set of named fields, each with its own type (see [Syntax](./syntax#record-type) for the declaration grammar):

```nextflow
record Sample {
    id: String
    fastq_1: Path
    fastq_2: Path?
}
```

A field can be marked as *optional* by appending `?` to its type.

A [record](#records) created with the `record()` function has the generic type `Record` and makes no guarantee about which fields it provides. A record type, by contrast, specifies a *minimum set of required fields*, and is used to make a stronger guarantee in a particular context, such as a workflow, process, or function input.

Record types are *structural* (or *duck-typed*): a record satisfies a record type if it provides at least the fields declared by that type. The record may have additional fields, and it does not matter how the record was created. A record is validated against a record type when it is supplied as an argument to a workflow, process, or function that declares a record type input, or cast to a record type with the [`as` operator](#type-operators):

```nextflow
def hello(sample: Sample) {
    println "Hello sample ${sample.id}!"
}

workflow {
    // error: missing `fastq_1` field required by Sample
    hello(record(id: '1', fastq_2: file('1_2.fastq')))

    // ok: all required fields are present (extra fields are allowed)
    hello(record(id: '1', fastq_1: file('1_1.fastq'), single_end: true))
}
```

Because matching is structural, two record types with the same fields and field types are interchangeable, even if they have different names.

### Enum types

An enum type is a user-defined type with a fixed set of named values (see [Syntax](./syntax#enum-type) for the declaration grammar):

```nextflow
enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}
```

Each value is accessed as a property of the enum type:

```nextflow
def day = Day.MONDAY
```

An enum type is useful for representing a value that can take only one of a fixed set of options, in place of a free-form string.

### Type casting

A value can be cast to a different type *explicitly* with the [`as` operator](#type-operators). The value must be convertible to the target type, or an error is raised:

```nextflow
def x = '1h' as Duration
def n = '42' as Integer
```

A value is also cast *implicitly* in the following situations:

- **Numeric coercion** -- when an [arithmetic operation](#arithmetic-operators) mixes an integer and a floating-point operand, the integer is coerced to a floating-point number.

- **Boolean coercion** -- in a condition, such as [if/else](#ifelse), [assertions](#assertions), or the [logical operators](#logical-operators), any value is coerced to a boolean according to its [truthiness](#booleans).

- **String coercion** -- when a value is interpolated into a [string](#strings), it is converted to a string with its `toString()` method.

[operator-page]: ./operator
[process-error-strategy]: ./process#errorstrategy
[process-page]: ../process
[script-page]: ../script
[stdlib-error]: ./stdlib-namespaces/global
[stdlib-types-float]: ./stdlib-types/float
[stdlib-types-integer]: ./stdlib-types/integer
[stdlib-types-iterable]: ./stdlib-types/iterable
[stdlib-types-bag]: ./stdlib-types/bag
[stdlib-types-list]: ./stdlib-types/list
[stdlib-types-map]: ./stdlib-types/map
[stdlib-types-record]: ./stdlib-types/record
[stdlib-types-set]: ./stdlib-types/set
[stdlib-types-string]: ./stdlib-types/string
[stdlib-types-tuple]: ./stdlib-types/tuple
[syntax-operators]: ./syntax#binary-expressions
[syntax-page]: ./syntax
[syntax-precedence]: ./syntax#precedence
[workflow-dataflow]: ../workflow#dataflow
[workflow-handlers]: ../notifications#workflow-handlers
[workflow-page]: ../workflow
