# Requesting feedback: new for-loop action for ROS 2 launch files to repeat entities

**URL:** https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021
**Category:** ROS General
**Tags:** ros2, launch
**Created:** [February 13, 2025, 1:31am UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021 "2025-02-13T01:31:33Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![christophebedard](https://sea2.discourse-cdn.com/flex022/user_avatar/discourse.openrobotics.org/christophebedard/32/1514_2.png) [@christophebedard](https://discourse.openrobotics.org/u/christophebedard)
#### Post date: [February 13, 2025, 1:31am UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021/1 "2025-02-13T01:31:33Z")

</div>

**tl;dr** I’m looking for feedback from ROS 2 users on this new for-loop action for launch (XML/YAML/Python), which can repeat entities N times based on the value of a launch argument, and provides an index that can be used for substitutions: [Add ForEach action to repeat entities using iteration-specific values by christophebedard · Pull Request #802 · ros2/launch · GitHub](https://github.com/ros2/launch/pull/802). Please post comments about the implementation on the PR and general comments about the solution here.

* * *

Being able to repeat entities (e.g., nodes) N times based on the value of a launch argument is a pretty commonly-requested feature. Example: [ros2/launch#499](https://github.com/ros2/launch/pull/499) and [[poll] Interest in ros2 launch action to support for-loops (e.g. IncludeNLaunchDescriptions())?](http://discourse.ros.org/t/poll-interest-in-ros2-launch-action-to-support-for-loops-e-g-includenlaunchdescriptions/20026). Some current solutions/workarounds include:

1. `OpaqueFunction`: [Launch Loop Support? [Feature Request] · Issue #499 · ros2/launch · GitHub](https://github.com/ros2/launch/issues/499#issuecomment-825055850)
2. Recursion: [GitHub - MetroRobots/rosetta\_launch: A guide to understanding launch files in ROS 1 and ROS 2](https://github.com/MetroRobots/rosetta_launch#12---recursion)

While these work, they involve some boilerplate code that has to be repeated in each launch file, and they make launch files more complex.

I’m [proposing a new `ForLoop` action (ros2/launch#802)](https://github.com/ros2/launch/pull/802), which also supports substituting the index value, e.g., `$(index i)` in XML or YAML. Examples:

> **XML**
>
> ```xml
> <launch>
> <arg name="num_robots" default="2" />
> <for len="$(var num_robots)" name="i" >
> <node pkg="test_tracetools" exec="test_ping" namespace="/pingpong_$(index i)" output="screen" />
> <node pkg="test_tracetools" exec="test_pong" namespace="/pingpong_$(index i)" output="screen" />
> </for>
> </launch>
> 
> ```

> **YAML**
>
> ```yaml
> launch:
> - arg:
> name: num_robots
> default: '2'
> - for:
> len: $(var num_robots)
> name: i
> children:
> - node:
> pkg: test_tracetools
> exec: test_ping
> namespace: /pingpong_$(index i)
> output: screen
> - node:
> pkg: test_tracetools
> exec: test_pong
> namespace: /pingpong_$(index i)
> output: screen
> 
> ```

> **Python**
>
> ```py
> import launch
> import launch_ros
> 
> def for_i(i: int):
> return [
> launch_ros.actions.Node(
> package='test_tracetools',
> executable='test_ping',
> output='screen',
> namespace=['/pingpong_', str(i)],
> ),
> launch_ros.actions.Node(
> package='test_tracetools',
> executable='test_pong',
> output='screen',
> namespace=['/pingpong_', str(i)],
> ),
> ]
> 
> def generate_launch_description():
> return launch.LaunchDescription([
> launch.actions.DeclareLaunchArgument('num_robots', default_value='2'),
> launch.actions.ForLoop(launch.substitutions.LaunchConfiguration('num_robots'), function=for_i),
> ])
> 
> ```

We discussed this a few months ago in a PMC meeting and I already got some feedback from some users, but I thought I’d post here to get more feedback from other ROS 2 launch users.

---

<div class="post-metadata">

### Author: ![smac](https://sea2.discourse-cdn.com/flex022/user_avatar/discourse.openrobotics.org/smac/32/6763_2.png) [@smac](https://discourse.openrobotics.org/u/smac)
#### Post date: [February 13, 2025, 1:35am UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021/2 "2025-02-13T01:35:08Z")

</div>

This is so great!

I’d be happy for Nav2 to be one of the first users of this once its merged into rolling [here](https://github.com/ros-navigation/navigation2/blob/main/nav2_bringup/launch/cloned_multi_tb3_simulation_launch.py)!

---

<div class="post-metadata">

### Author: ![christophebedard](https://sea2.discourse-cdn.com/flex022/user_avatar/discourse.openrobotics.org/christophebedard/32/1514_2.png) [@christophebedard](https://discourse.openrobotics.org/u/christophebedard)
#### Post date: [February 13, 2025, 11:44pm UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021/3 "2025-02-13T23:44:40Z")

</div>

Great to hear that you would find this useful!

Note that the action only accepts a length value (`N`). The generated index values then go from `0` to `N-1` (== `range()`). It looks like `cloned_multi_tb3_simulation_launch.py` does something a bit more complex: it expects a list of dicts as a launch arg string (parsed by `ParseMultiRobotPose`) and then iterates through the list. Unless I’m missing something, you could probably somehow leverage the `ForLoop` action for this, but it may not reduce the launch file code that much.

I’m wondering if there’s another loop mechanism that could be more useful in practice, e.g., iterate over something more complex than just `[0, N-1]`.

---

<div class="post-metadata">

### Author: ![smac](https://sea2.discourse-cdn.com/flex022/user_avatar/discourse.openrobotics.org/smac/32/6763_2.png) [@smac](https://discourse.openrobotics.org/u/smac)
#### Post date: [February 14, 2025, 12:32am UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021/4 "2025-02-14T00:32:54Z")

</div>

Got it! I think most naturally it would be a for-each loop, so perhaps that’s not quite what we have now in Nav2 😕

---

<div class="post-metadata">

### Author: ![christophebedard](https://sea2.discourse-cdn.com/flex022/user_avatar/discourse.openrobotics.org/christophebedard/32/1514_2.png) [@christophebedard](https://discourse.openrobotics.org/u/christophebedard)
#### Post date: [February 24, 2025, 12:55am UTC](https://discourse.openrobotics.org/t/requesting-feedback-new-for-loop-action-for-ros-2-launch-files-to-repeat-entities/42021/5 "2025-02-24T00:55:38Z")

</div>

Thanks for the feedback. I think that makes a lot of sense; I generalized it into a `ForEach` action to make it more flexible and much more powerful. It can now iterate over sets of parameters. It’s basically a string list of YAML structures/dictionaries, similar to what Nav2’s `ParseMultiRobotPose` does.

See the updated PR: [Add ForEach action to repeat entities using iteration-specific values by christophebedard · Pull Request #802 · ros2/launch · GitHub](https://github.com/ros2/launch/pull/802).

Examples:

> **XML**
>
> ```xml
> <launch>
> <arg name="robots" default="{name: 'robotA', id: 1};{name: 'robotB', id: 2}" />
> <for_each values="$(var robots)" >
> <log message="'$(for-var name)' id=$(for-var id)" />
> </for_each>
> </launch>
> 
> ```

> **YAML**
>
> ```yaml
> launch:
> - arg:
> name: robots
> default: "{name: 'robotA', id: 1};{name: 'robotB', id: 2}"
> - for_each:
> values: $(var robots)
> children:
> - log:
> message: "'$(for-var name)' id=$(for-var id)"
> 
> ```

> **Python**
>
> ```py
> import launch
> 
> def for_each(id: int, name: str):
> return [
> launch.actions.LogInfo(msg=f"'{name}' id={id}"),
> ]
> 
> def generate_launch_description():
> return launch.LaunchDescription([
> launch.actions.DeclareLaunchArgument(
> 'robots', default_value="{name: 'robotA', id: 1};{name: 'robotB', id: 2}"),
> launch.actions.ForEach(launch.substitutions.LaunchConfiguration('robots'), function=for_each),
> ])
> 
> ```

Default values are also supported, which I see is useful from looking at `ParseMultiRobotPose`. In Python, just set a default value for the corresponding callback function parameter. When using one of the frontends, provide an optional default value to the substitution: `$(for-var name 'default')`.
