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.

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

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

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)

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)

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:

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.