Primitive Effects

Note: The following text partially applies to Flix 0.53.0. It will not fully apply until Flix 0.54.0

Flix comes with a collection of pre-defined primitive effects. Unlike algebraic and heap effects, primitive effects cannot be handled and never go out of scope. A primitive effect represents a side-effect that happens on the machine. It cannot be undone or reinterpreted.

The most important primitive effect is the IO effect.

The IO Effect

The IO effect represents any action that interacts with the world outside the program. Such actions include printing to the console, creating, reading, and writing files, accessing the network, and more. The IO represents actions that change the outside world (e.g., modifying a file) but also actions that merely access the outside world (e.g., retrieving the current time). Unlike a pure function, a function with the IO effect may change behavior every time it is called, even if its arguments are the same. For example, reading the same file twice is not guaranteed to return the same result since the file may have changed between the two accesses.

The IO effect, and all other primitive effects, are viral. If a function has a primitive effect, all its callers will also have that primitive effect. That is to say, once you have tainted yourself with impurity, you remain tainted.

The Other Primitive Effects

In addition to the all-important IO effect, Flix has a small collection of pre-defined primitive effects. The point of these primitive effects is to provide more information about the specific actions a function can take. Except for the NonDet effect, all of effects below always come together with the IO effect.

  • Env: The Env effect represents actions that involve access to information about the environment, including information from the operating system (e.g., the name of the current user, the current working directory, and so on), as well as information about the hardware (e.g., the number of processors in the machine, the total amount of memory, and so on).

  • Exec: The Exec effect represents actions that involve process creation (i.e., spawning new processes that run outside the JVM), including using System.exec, the Process and ProcessBuilder classes, and dynamic library loading.

  • FsRead: The FsRead effect represents actions that read from the file system — for example, reading a file, reading its meta-data, or listing the contents of a directory.

  • FsWrite: The FsWrite effect represents actions that write to the file system — for example, creating a file, writing a file, or deleting a file.

  • Net: The Net effect represents actions that involve network access — for example, binding to a local port, connecting to a remote socket, or DNS resolution.

  • NonDet: The NonDet effect represents an almost pure computation. For example, a function that flips a coin is virtually pure; it has no side-effects. Yet, it may return different results, even when given the same arguments.

  • Sys: The Sys effect represents actions that interact with the JVM — for example, using the Runtime and System classes, class loaders, or reflection.

All of the above effects, except for the NonDet effect, always occur together with the IO effect. In particular, they capture a more precise aspect of the IO effect. For example, from a security point-of-view, it seems reasonable that a web server library should have the FsRead and Net work effects, but it would be worrying if it had the FsWrite and Sys effects. As another example, it seems reasonable that a logging library would have the FsWrite effect, but it would be a cause for concern if it also had the Exec and Net effects.

The above effects represent dangerous actions except for IO, Env, and NonDet, which are relatively harmless. Exec allows arbitrary process execution, FsRead can be used to access sensitive data, FsWrite can be used to trash the filesystem, Net can be used to exfiltrate data, and Sys via reflection allows access to all of the previous. We should always be suspicious if unknown or untrusted code uses any of these effects.

The primitive effects are mostly disjoint, but not entirely. For example, we can use the Exec effect to indirectly gain access to the file system. The FsRead effect may allow us to access a mounted network drive, a form of network access. Ultimately, whether one effect can emulate another depends on what side channels the underlying operating system allows. The point of the effect system is that if a function does not have the FsWrite effect, it cannot write to the file system using the ordinary file APIs available on the JVM.

Where do Primitive Effects Come From?

The Flix compiler ships with a built-in database that maps classes, constructors, and methods in the Java Class Library to primitive effects. For example, the database assigns the Exec effects to every constructor and method in the java.lang.Process, java.lang.ProcessBuilder, and java.lang.ProcessHandle classes.