System
An eco system program is an ordinary Elm Platform.Program that runs as a command-line tool
or a server, and this module is what makes it one: the Environment it starts
with, and the controls that act on the whole running program, such as its exit code and the
signals sent to it.
The flags of such a program are an Environment. eco recognises a main whose flags type is
System.Environment and builds the environment itself when the program starts, so any flags
JSON a host supplies is ignored. The environment tells you which platform the program is
running on, which arguments it was started with and when it started, and gives you its standard
input, output and error streams.
A long-running program, one that keeps state, reacts to messages and listens to
subscriptions, is made with program:
main : Platform.Program System.Environment Model Msg
main =
System.program
{ init = \env -> ( initialModel env, Cmd.none )
, update = update
, subscriptions = subscriptions
}A simple program runs a single task and then ends. It is made with
simpleProgram:
main : Platform.Program System.Environment () ()
main =
System.simpleProgram
(\env -> Stream.logLine "Hello, world!" env.stdout)A program runs while it is waiting for something it asked for, and ends when it is not. It is
waiting while a task it started has not finished, such as a read, a write, an accept, a
sleep, an HTTP request or a child process it spawned; while it subscribes to something that can
still deliver, such as new connections on an open listener, messages on an open WebSocket or
changes to a watched file; and while it owes an answer to an HTTP request it was given.
Opening something does not keep a program running: an open listener with no accept waiting
and no subscription, or an open connection with no read or write in progress, does not.
Connections, datagrams and requests that arrive while nothing asks for them are held for later
and do not keep a program running either. Some subscriptions never keep a program running,
because they wait for things the program did not ask for; their docs say so.
When nothing is left to wait for, the program has an empty event loop and ends, unless it
subscribes to onEmptyEventLoop, which is told once and can ask to be
told again with rearmEmptyEventLoop. exit and
exitWithCode end the program at once, whatever it is waiting for.
The same rule covers the elm/* packages. A Process.sleep keeps the program running until it
ends, a Time.every subscription while it exists, and an elm/http request while it is in
flight. Http.track adds nothing of its own: a tracker with no request in flight does not keep
the program running.
eco compiles a program either to native code or to JavaScript run by Node. Where the two
behave differently, the docstrings say so. A native program can also be embedded in a host
application, which then owns the process. An embedded program does not end when it runs out of
work: it runs until the host stops it or it calls exitWithCode. Embedding
also changes what exiting and signals do, as the functions below describe.
Programs
program :
{ init : Environment -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
}
-> Program Environment model msgsimpleProgram : (Environment -> Task x a) -> Program Environment () ()Creates a simple program: one that runs the task run makes from the
Environment, and has no state, messages or subscriptions.
The task's result is ignored. If the task fails, the program's exit code is set to 1, as
setExitCode sets it; if it succeeds, the exit code is left as it is, which is
0 unless the task set another. The program ends as any program does, so it waits for IO the
task started even after the task has finished. To end with a particular non-zero exit code,
finish the task with setExitCode:
main : Platform.Program System.Environment () ()
main =
System.simpleProgram
(\env ->
case List.drop 1 env.args of
[] ->
Stream.logLine "usage: greet NAME" env.stderr
|> Task.andThen (\_ -> System.setExitCode 2)
name :: _ ->
Stream.logLine ("Hello, " ++ name ++ "!") env.stdout
)Environment information
type alias Environment =
{ platform : Platform
, cpuArchitecture : CpuArchitecture
, applicationPath : Path
, args : List String
, startTime : Posix
, stdout : Writable Bytes
, stderr : Writable Bytes
, stdin : Readable Bytes
}What a program is told about itself when it starts: the platform it runs on, how it was started, and its standard streams.
applicationPathis the path of the running program. On the native backend it is the executable, with symbolic links resolved where the system allows it, orPath.emptywhere it cannot be read, which includes every system other than Linux and macOS. On the JavaScript backend it is the path of the compiled JavaScript file, with symbolic links resolved where possible, or of thenodeexecutable when the program is not loaded as a CommonJS module. The text is read withPath.fromPosixStringon every system.argsholds the command line arguments. On the native backend, on Linux, macOS and Windows, it is the process's Cargv, so it starts with the program as it was invoked; elsewhere the list is empty. Embedded in a host application, it is the host'sargv. On the JavaScript backend it is Node'sprocess.argvwithout thenodeexecutable, so it starts with the script.startTimeis read once, when the environment is built just beforeinit, soinitdoes not need to ask for the time.stdout,stderrandstdinare three separate streams over the process's standard output, error and input; theStreammodule says what can be done with them, andStream.readLinereads standard input a line at a time. Closing or cancelling one of them ends that stream but leaves the process's standard file descriptor open.
environmentVariables : Task x (Dict String String)Reads the environment variables of the running process, mapping each variable's name to its value.
The variables are read when the task runs, not when the program starts. On the native backend
an entry with no = is read as a name with the empty value.
type Platform
= Win32
| Darwin
| Linux
| FreeBSD
| OpenBSD
| SunOS
| Aix
| UnknownPlatform StringThe operating system a program is running on, spelled as Node's process.platform spells
it. Win32 is Windows and Darwin is macOS; Linux, FreeBSD, OpenBSD, SunOS and
Aix are the systems they name.
UnknownPlatform carries the name of any other platform, as the backend reported it. On the
native backend the platform is fixed when the program is compiled, and one this type does not
name is reported as UnknownPlatform "unknown".
type CpuArchitecture
= Arm
| Arm64
| IA32
| Mips
| Mipsel
| PPC
| PPC64
| S390
| S390x
| X64
| UnknownArchitecture StringThe processor architecture a program is running on, spelled as Node's process.arch spells
it. X64 is 64-bit x86 and IA32 32-bit x86; Arm and Arm64 are 32-bit and 64-bit Arm;
Mips and Mipsel are big-endian and little-endian MIPS; PPC and PPC64 are 32-bit and
64-bit PowerPC; S390 and S390x are 31-bit and 64-bit IBM mainframe (s390) processors.
UnknownArchitecture carries the name of any other architecture, as the backend reported it.
On the native backend the architecture is fixed when the program is compiled; 64-bit RISC-V is
reported as UnknownArchitecture "riscv64", and any other architecture this type does not
name as UnknownArchitecture "unknown".
Exit
exitWithCode : Int -> Cmd msgEnds the program at once with exit code.
It does not wait for work still in progress, such as an HTTP request, a file write, a stream
write that has not yet succeeded, or a value Stream.enqueue has queued but not yet written,
so it is for a state where continuing makes no sense; to end once that work is done, use
setExitCode instead. The exit code can be read by the process that started
the program.
On the native backend, when the program is embedded in a host application, the process is not
ended: the program stops and the host receives code.
setExitCode : Int -> Task x ()Sets the exit code the program ends with, without ending it.
A later setExitCode replaces the code, and exitWithCode ends the program
with its own.
Subscriptions
onEmptyEventLoop : msg -> Sub msgCreates a subscription that receives msg when the program has an empty event loop:
nothing it is waiting for is left, so the program would otherwise end.
The message arrives once. Work started while handling it runs to completion, and the next time
the event loop is empty the program ends. To receive the message again at the next empty
event loop, issue rearmEmptyEventLoop. If the program has several
onEmptyEventLoop subscriptions, each receives its message at the same moment.
Whether the message is due is one setting for the whole run of the program. It starts on, so a
program that subscribes some time after it starts still receives the message once. Delivering
the message turns it off, and only rearmEmptyEventLoop turns it on again: subscribing anew
after a delivery is not enough.
The subscription does not keep the program running. On the native backend it never fires when the program is embedded in a host application.
rearmEmptyEventLoop : Cmd msgAsks for onEmptyEventLoop to deliver its message again the next
time the program has an empty event loop.
Issue it while handling that message, or in any later update before the event loop is next
empty, for example once more jobs have been queued. If the program issues it and then starts
no work, the event loop is empty again at once, so the message arrives again at once; a
program that issues it on every delivery without starting work never ends. Issuing it when
there is no onEmptyEventLoop subscription has no effect until the program subscribes.
onSignalInterrupt : msg -> Sub msgCreates a subscription that receives msg when the process is sent an interrupt
signal (SIGINT, the signal a terminal sends on Ctrl+C).
While the subscription exists, the signal no longer ends the program, so ending it is up to the program. The subscription does not keep the program running. On the native backend it has no effect on Windows or when the program is embedded in a host application: it never fires, and the signal is handled as it would be without it.
onSignalTerminate : msg -> Sub msgCreates a subscription that receives msg when the process is sent a terminate
signal (SIGTERM, the request to shut down that service managers and the kill command send).
While the subscription exists, the signal no longer ends the program, so ending it is up to the program. The subscription does not keep the program running. On the native backend it has no effect on Windows or when the program is embedded in a host application: it never fires, and the signal is handled as it would be without it.
Creates a long-running program from an
init, anupdateandsubscriptions, asPlatform.workerdoes, with the program'sEnvironmentas its flags.The program runs while it is waiting for something, as the module documentation describes, and ends when it has an empty event loop that
onEmptyEventLoopdoes not take over.