Jsonnet is a configuration language.
What are some examples of configuration languages?
JSON is a simple configuration language. Lua's immediate ancestor, Sol, is a configuration language. Lua can be used as a configuration language.
Configuration languages are a kind of domain-specific language (DSL) A DSL is a language adapted, specialized, to a particular domain. For example, Knuth's TeX and PostScript are DSLs specialized for typesetting. The domain of configuration languages is configuration.
What is configuration?
Configuration is a subdiscipline within programming. It deals with making the last few decisions of the project, any and all decisions that you have postponed for one reason or another to this last stage.
For example, imagine a feature toggle switch. Your decision to turn on the feature has a risk/reward tradeoff. You know you will learn late in the process of launching a version of software whether a particular new feature actually turns out to work reliably. If it does work reliably, then you would like to launch with it turned on (increasing your reward for launching). If it does not work, then you would like to launch without it (decreasing your risk).
Alternatively, you may want the system to use a particular username and password to access a back-end database, but you don't want to publish the particular username and password to the entire development team. For example, in step 3 of Wordpress's famous five-minute install, you edit a file named wp-config.php, to set the value of the DB_USER and DB_PASSWORD variables to specific values. The Wordpress dev team cannot get into your database, and that's good. They chose to use configuration in order to give you this exclusive access feature.
Finally, some applications can be built by wiring together various relatively standard pre-built components. Wiring together pre-built components to create a mash-up is often also considered configuration.
How are configuration languages similar?
They support flags and/or key-value pairs. For example, INI files use the equals sign, while JSON uses a colon. Jsonnet uses both. It uses equals to initialize variables, and uses colon for associating keys to values in dictionaries.
They prioritize their literal-data sublanguage. Almost all programming languages have a facility for including literal-data in their code. At the low end of support for literal data, very old versions of the C programming language only have literal numbers like 12
and literal strings "three"
. JSON is actually the literal-data sublanguage of JavaScript. It has literal numbers, literal strings, literal arrays like [1, 2, 3]
and ["one", "two", "three"]
, and literal dictionaries like {"x": 1, "y": 2}
.
This is perhaps a subtle point, but: They prioritize constructing things more than destructing them. Features like pattern-matching, such as in Prolog:
total([], 0).
total([X|Xs], Z) :- total(Xs, Y), Z is X + Y.
or in Haskell:
total [] = 0
total x:xs = x + total xs
are ways of taking apart, searching and/or summarizing data structures, and configuration languages can often avoid doing that at all. They can usually manage to build the final configuration out of smaller things (instead of building a data structure even larger than the final configuration, and then searching and/or summarizing it to build the
Lack of inputs from the environment (such as via user input, environment variables, system calls, or network requests) is often a virtue, so-called "hermetic". There is probably a family of programming languages where the best analog to user input is actually a section of the source code. "definition definition definition ACTUAL_USE_OF_THING". So configuration languages either have fewer inputs from the environment, or de-prioritize making inputs easy to use, or mark inputs from the environment with verbose syntax. Verbose syntax of inputs helps a reviewer answer the question "How hermetic is this?".
How can I learn Jsonnet?
I am trying to do some of Rosetta Code's tasks in Jsonnet. Some are difficult or impossible, due to the constraints of the configuration language, but many are possible.
Toe-stubbing points. Javascript uses "var", but Jsonnet uses "local", and it doesn't end with a semicolon, it ends with a comma.
Array concatenation
In Jsonnet, array concatenation is just +
:
['a', 'b', 'c'] + [1, 2, 3]
Array length
In Jsonnet, array length is a function from the standard library:
std.length(['apple', 'orange'])
Factorial
You can write a simple recursive function like factorial pretty straightforwardly:
local factorial(n) = if n == 1 then 1 else n * factorial(n-1);
factorial(10)
Factors of an integer
There is a list comprehension syntax similar to python:
[ i for i in std.range(1, 100) if 100 % i == 0 ]
Another way to do it, using a filter function in the standard library and an anonymous function:
std.filter(function(i) 100 % i == 0, std.range(1, 100))
Fizzbuzz
There's a map function in the standard library:
std.map(function(i)
if i % 15 == 0 then
'FizzBuzz'
else if i % 3 == 0 then
'Fizz'
else if i % 5 == 0 then
'Buzz'
else
i,
std.range(1, 100))
Function definition
You can define a local function with fairly straightforward syntax:
local multiply(a, b) = a * b;
multiply(2, 3)
Greatest element of a list
To scan a list, you can use foldl (or foldr).
std.foldl(std.max, [1, 7, 2, 3, 4, 5, 6], 0)
Multiplication table
Creating an upper-triangular multiplication table using list comprehensions:
[ [ i*j for j in std.range(1, 12) if i <= j ] for i in std.range(1, 12) ]
Nth root
Jsonnet isn't really intended for this kind of floating point / numeric computation, but finding the nth root demonstrates recursive helper functions with lexical scope:
local nthRoot = function (A, n, epsilon)
local helper = function (guess)
local delta = (A/std.pow(guess, n-1) - guess)/n;
if std.abs(delta) < epsilon then
guess
else
helper(guess + delta);
helper(1);
local sqrt = function (A)
nthRoot(A, 2, 0.001);
sqrt(2)
Tokenize a string
There's a standard library function for splitting a string:
std.split('Hello,How,Are,You,Today', ',')
Zero to the zeroth power
There's a standard library function for computing exponents:
std.pow(0, 0)
Laziness
Lazy evaluation means that the Jsonnet evaluator can be imagined as substituting equations into other equations in order to compute a particular result. This allows the source code to contain particular kinds of infinite loops, without causing the evaluator go into infinite looping (or at least, not necessarily).
In this code, "fibonacci" can be imagined to be an infinitely long list of numbers. fibonacci.first, fibonacci.rest.first, fibonacci.rest.rest.first
and so on will all work fine. Laziness means that we can take the first ten elements of the list of all the fibonacci numbers, and because there is a demand for them, the evaluator will use the definitions to figure out what they are.
local listsum(l1, l2) = {
first: l1.first + l2.first,
rest: listsum(l1.rest, l2.rest)
};
local fibonacci_rest = { first: 1, rest: listsum(fibonacci, fibonacci_rest) },
fibonacci = { first: 0, rest: fibonacci_rest };
local take(n, list) =
if n == 0 then
[]
else
[list.first] + take(n - 1, list.rest);
take(10, fibonacci)
What about the special stuff?
Jsonnet has special stuff too, including self and super and a kind of inheritance which is associative and commutative and idempotent?!? plus reflection? But I don't really understand how or when to use those yet.
I think one of the difficulties in learning to use the "special stuff" is that good style is to not use it, unless and until you really need to. Since small things can be handled with builtin data structures and a few functions, there are few toy examples of good usage of special stuff. (Note that the above examples are not necessarily good style! I'm not skilled enough to know what is good style in Jsonnet yet.)