Constants in EK9
A constant is a named, fixed literal value of one of the basic built in types. Constants are deliberately constrained: as with most of EK9, the language has a definite idea of how and where each construct should be used.
- Your own classes, records and other types cannot be constants, and nor can a List or Dict: only literal values of the basic built-in types can.
- To use a class or record as a single global value, use a component and dependency injection.
Why EK9 does it this way. It can feel inconvenient at first, but over time it reduces coupling, because code is no longer linked directly to a specific class/record/component. A constant is for a fixed concept, value or default, nothing more.
A constant is immutable: its value cannot change. You can still pass a constant (or a literal) to a function or method that is not pure, and that callable may alter the value it receives. The constant itself is not altered, and there is no 'exception' or 'immutability' error at compile time or run time: in effect a constant or literal is copied as it is passed in, and becomes a variable. That keeps the constant immutable and keeps arguments passed by reference. The same applies when a callable returns a constant or literal: the value is copied first.
Constant Declarations
defines constant limitNumberOfRetries <- true updateErrorLog <- false delimiter <- ':' author <- "Frank Jones" maxNumberOfRetries <- 10 minTemperatureAllowed <- -2 PI <- 3.142 bitMask <- 0b01110001 noon <- 12:00 justAfterNoon <- 12:00:01 //ISO 8601 i.e P[n]Y[n]M[n]DT[n]H[n]M[n]S maxRenewalDuration <- P3Y2M6DT12H15M6S twoYears <- P2Y twoMonths <- P2M minusTwoDays <- P-2D twoHours <- PT2H minusTwoHours <- PT-2H twoMinutes <- PT2M timeoutPeriod <- 250ms newMillennium <- 2000-01-01 ek9CreatedDateTime <- 2018-01-31T01:30:00-05:00 maxPayment <- 300000#USD defaultColour <- #AB6F2B twoMeters <- 2m matchSteves <- /[S|s]te(?:ven?|phen)/ //A comment //And another
Full source: constants_introduction.ek9 - Download constants_introduction.ek9
Every constant is a literal, by design; built in types covers each type above. Constants are for fixed values and are meant to stay plain.
Use of Constants
Anywhere in the same module (later in the same source file, as here, or in another EK9 source file that declares the same module), use a constant by its name.
Same Module
defines function areaOfCircle() -> diameter as Float <- result as Float: PI * (diameter/2)^2 circumferenceOfCircle() -> diameter as Float <- result as Float: PI * diameter
Full source: constants_introduction.ek9 - Download constants_introduction.ek9
PI is used directly.
Different Module
You will often define a range of constants in one module and use them throughout others; that reduces the proliferation and duplication of fixed values. The options below apply to any construct in another module, not only to constants. To use PI from another module, you have two options.
Option 1
#!ek9 defines module site.constants.part.one defines function printPI() Stdout().println($site.constants.introduction::PI)
Download constants_qualified.ek9
The function 'printPI' in the 'site.constants.part.one' module refers to the constant PI in the 'site.constants.introduction' module by its full name.
Option 2
Qualifying PI with its module every time becomes tedious. Declare a reference instead, and the constant or type can be used directly.
#!ek9 defines module site.constants.part.two references site.constants.introduction::PI defines function printPI() Stdout().println($PI)
Download constants_referenced.ek9
Summary (TL;DR)
Constants are plain, but don't overlook them. They give you compiler support: mistype a constant's name and the compiler reports it; mistype a fixed value repeated through your code and it cannot.
There is a balance to find. Some developers see a constant for a value used once in a small program as 'over engineering'. Others see values such as a 'delimiter' for file processing (as in the standard types example) as a key part of the solution, to be 'elevated' and made more prominent.
A constant is not only about reuse: it is fixed and immutable, and above all it has a meaningful 'name'. Designing software is also about making the solution obvious to people, and meaningful names help.
Balance
If you have worked with a range of languages or industries, you may have seen developers start simple and build ever more complex webs of abstractions, until no one can 'see the wood for the trees'. Everything becomes a façade or an interface, and higher order functions use other higher order functions to create yet more higher order functions (now where was I?).
Too many levels of abstraction create cognitive load, so be sure each one is worth creating. Constants are one of the starting points for abstraction: a developer reading some processing now has to jump to the constant to see its value. Be sure that extra step is necessary. Finding this balance is one of the hardest parts of software development.
Constant Organisation in Multi-File Modules
When a module spans several source files, the compiler requires its constants to be consolidated rather than scattered:
- E11066: constants in 3 or more files of a module: consolidate them into a single constants file
- E11067: 10 or more constants spread across 2 or more files: use a dedicated constants file
Single-file modules are exempt: their constants are already in one place. The built-in module
(org.ek9.lang) is also exempt, as its structure is intentional. Both errors are reported on the
defines module line of the module's first file (by file name). When a module breaks both rules,
that line shows E11066, and consolidating the constants fixes both.
Best Practice
For any multi-file module, create a dedicated constants file (for example moduleConstants.ek9)
and put every defines constant block there. It is the one authoritative place for the module's
fixed-value vocabulary.
Example: Bad Organisation
Three files of ONE module, compiled together. Each file is fine on its own; the error exists only across them, so it is reported on the first file.
Fails with E11066: constants should be consolidated into a single file
constants_scattered_config.ek9
#!ek9 defines module bad.site.constants.scattered defines constant MAX_RETRIES <- 3
constants_scattered_logging.ek9
#!ek9 defines module bad.site.constants.scattered defines constant LOG_LEVEL <- "DEBUG"
constants_scattered_networking.ek9
#!ek9 defines module bad.site.constants.scattered defines constant DEFAULT_TIMEOUT <- 30
With constants scattered across three files you cannot see the module's whole vocabulary at once, and developers may create duplicates or name constants inconsistently.
Example: Good Organisation
constants_consolidated_constants.ek9
#!ek9 defines module site.constants.consolidated defines constant MAX_RETRIES <- 3 DEFAULT_TIMEOUT <- 30 LOG_LEVEL <- "DEBUG"
constants_consolidated_networking.ek9
#!ek9 defines module site.constants.consolidated defines function connectionTimeout() as pure <- seconds as Integer: DEFAULT_TIMEOUT retryPolicy() as pure <- policy as String: `retry ${MAX_RETRIES} times, logging at ${LOG_LEVEL}`
constants_consolidated_settings.ek9
#!ek9 defines module site.constants.consolidated defines program ShowSettings() stdout <- Stdout() stdout.println(retryPolicy()) stdout.println(`timeout ${connectionTimeout()} seconds`)
All the constants are in one dedicated file. The other files hold only other constructs (functions, classes,
records, programs and so on) and use the constants by name, so the module's vocabulary is visible in one
place. Running ShowSettings prints:
retry 3 times, logging at DEBUG timeout 30 seconds
Conclusion
Used with care, constants cut out simple errors and make significant fixed values stand out in the code, for other developers and for you.
Next Steps
The next section, flow control, shows EK9's basic control structures.