Primitive Effects
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
Note: As of Flix 0.65.0 we are making changes to the primitive effects, hence the text below may be slightly outdated.
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 the effects below always come together with the IO
effect.
-
Env: The
Enveffect 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
Execeffect represents actions that involve process creation (i.e., spawning new processes that run outside the JVM), including usingSystem.exec, theProcessandProcessBuilderclasses, and dynamic library loading. -
FsRead: The
FsReadeffect 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
FsWriteeffect represents actions that write to the file system — for example, creating a file, writing a file, or deleting a file. -
Net: The
Neteffect represents actions that involve network access — for example, binding to a local port, connecting to a remote socket, or DNS resolution. -
NonDet: The
NonDeteffect 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
Syseffect represents actions that interact with the JVM — for example, using theRuntimeandSystemclasses, 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.