Object-oriented (OO) design is a very powerful approach for modelling real-world problem domains, but out of the programming languages popularly thought of as "OO", few readily lend themselves to authentic object thinking - at least, that appears to be the rationale behind the development of eolang.
At first glance, the rhetoric seems far too familiar. Aggressive overselling - the future of OOP, indeed - by way of catastrophism could well be mistaken for the bread and butter of marketing in our field. Or, as Glass pointedly put it, "Hype is the plague on the house of software.". However, the developers follow up with an unusual reservation: They don't want their language to become mainstream. Instead, EO aims to be a tool for demonstrating that "true" or "pure" OO is feasible.
This goal sure looks ambitious (and quite admirable, to be honest) - and given the state of the typical project written in "OO" languages, I can definitely empathise with their notion of inauthenticity. The paradigm is often criticised for design constructs that run in counter with the fundamental concepts, simply because the program was implemented in, e.g., Java or C#.
Even if we ignore the philosophical aspects, eolang does look like a fun puzzle. Picking up a new language is likely to alter some way of how you think to approach some problem or another, but EO takes this a few steps further. The syntax is obviously far from any C dialect and many common constructs (e.g., classes, mutability and operators) are forbidden by design.
Many of these constraints are bound to give rise to their fair share of controversy. However, the central character - Yegor Bugayenko, chairman of the International Conference on Code Quality (ICCQ) - ought to hardly be a stranger to such disagreements: He has promoted and defended these design principles for years and implemented them (discounting the limitations of conventional languages) in more than a few open source projects.
But that's enough background. Let's get started!
Tradition dictates that we begin with the one program that every programmer knows: Print a hardcoded text to standard output. My solution in EO can be found here). To compile and run the code, respectively, execute the following commands.
mvn -f helloworld/pom.xml clean compile
java -cp helloworld/target/classes:helloworld/target/eo-runtime.jar org.eolang.phi.Main sandbox.app
It's difficult to get a feel for a language from such a limited example, but
it still suffices to highlight a couple of thing of note. Besides some choices
in the language design (e.g., enforced indentation and no static typing
), we see three peculiar constructs: []
, >
and @
.
The first construct, []
, tells us what the object needs to know to do what it
does. In this case, the construct is empty because our program already knows
everything it needs to know. That is, all information required to print "Hello
world!" to standard output is already encapsulated in the app
object.
Next, >
is related to the previous construct. Similar to other arrows, it
directs the left operand into the one on the right. For instance, [] > app
can be understood as someone needs to know something ([]
i.e., nothing) and
that someone is app
. However, we also note that the operator is not only used
in conjunction with []
, for example on the line stdout > @
.
Finally, the third construct, @
, is EO's symbol for a decoration. For
example, we can interpret stdout > @
as the enclosing object (app
)
commiting to expanding its contact to include that of stdout
- although app
will fulfill those parts by subcontracting stdout
.
Next, having considered these three constructs in isolation, let us reassemble
the original program. It now becomes clearer that the primary hierarchy in
eolang - i.e., what the indentation emphasises - is encapsulation. Our program
is an object, app
, that encapsulates a representation of standard output,
stdout
, which in turn encapsulates a text, "Hello world!"
. The equivalent
in a more conventional (pseudeo-)syntax, might look similar to below and is
illustrated in Fig 1.
class app {
origin := new stdout(new text("Hello world!"));
function apply() {
origin.apply();
}
}
What we see is that our inner-most objects are enclosed by another object that provides just a thin sheet of functionality. Then that object, in turn, is similarly enclosed. With each level, our model becomes richer and richer while keeping each component small and managable.
From this point of view, we can see that EO has the nice property of being
lazy. The concern of constructing our program instance (i.e., eo := new app()
) is cleanly separated from the concern of execution (i.e., eo.apply()
). Like
a clockwork toy of old, we prepare our program by winding it up and then, when
the time is right, we release it and let it live its own life.
Thus, our first EO exercise is concluded. So far, we've seen but a snippet of code, but already have we encountered a somewhat esoteric syntax and language constructs. Already, I think it's safe to say that EO will challenge your average programmer's cognition - and in a quite good way at that!
Let's continue our exploration with a little maths game that is often used in children's education: Friends of Ten. The exercise is nothing special - when asked about a number [1, 9], respond with the number that would bring their sum to 10 - but should introduce us to some important fundamentals for writing useful programs. As opposed to our "Hello World!" program, let's go about this is a slightly more structured way.
First, we create the simplest possible "Friend of Ten" we can imagine: A program that neither validates input nor prints. The code for this program can be found here.
Of course, we want to create some (unit) tests for this object. This requires
us to first add a dependency on JUnit in
our POM file. Second, we create a small object
that encapsulates our test. In
this example, the test will determine
if our friend
object can respond with the correct answer (i.e., 7
) when
asked who the pair of 3
is.
By compiling and executing our test (note the "Test" was appended to the method name), we can see that the exit code indicates that the program could be run successfully.
mvn -f friendsoften/pom.xml clean compile
java -cp friendsoften/target/classes:friendsoften/target/eo-runtime.jar org.eolang.phi.Main sandbox.pairThreeTest
echo $?
As an aside, here is a test that will
fail when run. Please note how executing incorrectPairTest
will throw an
exception and return a non-zero exit code.
So far, we've implemented friend
, which is the simplest possible solution to
our problem. Next up, we want a solution that's a little bit stricter: As
mentioned above, for the children's game, the domain of our function is [1, 9].
However, we don't want to modify our object to impose this rule, as that would
make it too rigid. Instead, we create a new object, restricted
, that will
delegate to the former (code and
tests).
Note that restricted
, unfortunately, does two things: Verifies the range
and transforms the result from an integer to a string. The reason is that I
(so far) haven't found out how to indicate user errors. For now, we'll simply
return a special string instead.
Third and finally, we create the entry point to our program: The app
object.
This is rather similar to the one created in the previous exercise, but it does
accept input from standard in. To try it out, compile and execute it with a few
different values.
mvn -f friendsoften/pom.xml clean compile
java -cp friendsoften/target/classes:friendsoften/target/eo-runtime.jar org.eolang.phi.Main sandbox.app 3
java -cp friendsoften/target/classes:friendsoften/target/eo-runtime.jar org.eolang.phi.Main sandbox.app -3
java -cp friendsoften/target/classes:friendsoften/target/eo-runtime.jar org.eolang.phi.Main sandbox.app 100
As before, the object composition of our program (the encapsulations) is illustrated below, in Fig 2.
To reiterate, this exercise demonstrated how we can decompose a problem to be solved by multiple small objects that collaborate. We partitioned them into their own files and designed their boundaries such that the system is better equipped to evolve to meet contingent changes. Additionally, we had a first look into the fundamentals for unit testing our objects.
mvn compile
java -cp target/classes:target/eo-runtime.jar org.eolang.phi.Main sandbox.app YEAR
where YEAR
is, e.g., 2020 or 2021.