Description
Currently, all the code to run a .step file must be included in the file itself. An exception are the framework functions (readline
, println
, etc).
In order for others to be able to share and reuse code, a way to import functions from other files is needed.
Justification
One obvious drawback is that it makes the language more complex in general.
Depending on the chosen design, additional keywords may be needed.
On the other hand it allows programmers to explore the widely accepted concept of code splitting.
Not only on a function-by-function basis, but also on a higher level, allowing more complex programs.
As many other languages also use some sort of import structure, STEP would further allow novice programmers to get used to this concept.
Design
Possible implementations could look like the following:
Explicit file imports
Imported files are expected to be found at the relative paths to the importing file.
import "utils/helpers.step"
string connectionString = getConnectionString()
This could also allow glob patterns to be used:
import "loggers/*.step"
logInfo("something important")
logError("this error has occurred: ", EOL, "some error")
Implicit file imports
string connectionString = utils.helpers.getConnectionString()
utils.helpers.<func>
would then be converted to a path (utils/helpers.step
) and a function with the name of <func>
is expected to be defined in the file.
logger.info.println("information log")
logger.error.println("an error occurred: ", "error description")
This would also open the possibility to "invokable" files.
The top level statements in a file could be interpreted as the function body:
calc.step
number a = parse("number", args[0])
number b = parse("number", args[1])
number c = parse("number", args[2])
return c * (a + b) / b
main.step
number a = 5
number b = 12.5
number c = parse("number", readline())
number result = calc(a, b, c)
// or alternatively:
number result = calc([a, b, c])
// or
number result = calc.invoke([a, b, c])
println("Result: ", result)
With explicit exports
Explicitly exporting members gives the programmer more control over what is exposed from their code.
This aligns with the goal of providing reusability as internal/helper methods can be hidden from the public API.
calc.step
function minmax = (number a, number b, number c) {
if (c < a) {
return a
}
if (c > b) {
return b
}
return c
}
function doCalc = (string x, string y) {
number xNum = parse("number", x)
number yNum = parse("number", y)
return minmax(0, 10, xNum) * minmax(10, 20, yNum)
}
export doCalc
// or, to re-use existing keywords:
return doCalc
main.step
// doesn't import minmax from calc.step
import "calc.step"
println(doCalc(readline(), readline()))
All combined
The above proposals don't exactly need to be implemented on their own. Another approach would be a combination of all of them:
calc.step
function minmax = (number a, number b, number c) {
if (c < a) {
return a
}
if (c > b) {
return b
}
return c
}
function doCalc = (string x, string y) {
number xNum = parse("number", x)
number yNum = parse("number", y)
return minmax(0, 10, xNum) * minmax(10, 20, yNum)
}
export doCalc
math.step
number pi = 3.141592
function cube = (number a) {
return a ^ 3;
}
export pi
export cube
main.step
// explicitly import math.step
import 'math.step'
// use members from math.step
println(cube(pi))
// implicitly import calc.step (only doCalc is visible)
println(calc.doCalc(readline(), readline()))
With import aliases
import("math.step", math)
import("calc.step", calc)
println("3 cubed: ", math.cube(3))
println(calc.doCalc("123", "456"))