Avoiding side effects#

It is strongly discouraged to use side effects in code that runs as part of a pipeline. This applies to, among others, file output, setting global variables, or communicating over a network. The reason is that side effects rely on code running in a specific order. But pipelines in Sciline have a relaxed notion of time in that the scheduler determines when and if a provider runs.

File output#

Files typically only need to be written at the end of a pipeline. We can use Pipeline.bind_and_call to call a function which writes the file:

[1]:
from typing import NewType

import sciline

_fake_filesystem = {}

Param = NewType('Param', float)
Data = NewType('Data', float)
Filename = NewType('Filename', str)


def foo(p: Param) -> Data:
    return Data(2 * p)


def write_file(d: Data, filename: Filename) -> None:
    _fake_filesystem[filename] = d


pipeline = sciline.Pipeline([foo], params={Param: 3.1, Filename: 'output.dat'})

pipeline.bind_and_call(write_file)
[2]:
_fake_filesystem
[2]:
{'output.dat': 6.2}

We could also write the file using

[3]:
write_file(pipeline.compute(Data), 'output.dat')

But bind_and_call allows us to request additional parameters like the file name from the pipeline. This is especially useful in combination with generic providers or parameter tables.

Why is this better than writing a file in a provider? Using bind_and_call guarantees that the file gets written and that it gets written after the pipeline. The latter prevents providers from accidentally relying on the file.