I was watching some of the videos from the Lang.NEXT conference, which are available online, one of the themes being this panel discussion. During the process, I started to take notes, because it was too much for my brain to handle it all at ones. This post is a summary of my thoughts after having watched the video.
Web and Cloud Programming
"Web and cloud programming" was the title of the discussion. Cloud programming is quite a noncommittal expression. What do we mean by "cloud programming"? Is it programming on the cloud (with the IDE in the cloud)? or programming applications for the cloud (but that can be just any web app right)? It turns out this is just a term to depict the programming in distributed environment.
Programming in Distributed Environment
Programming in distributed environment is a much better term - but again, it might not be completely clear. The majority of today's applications are not sequential anymore. The code flow of the program is parallel, asynchronous and the program has to react to external events. The code and the application itself is distributed. It might be distributed over several cores, or nodes or it might be just server side - client side code separation. You can have code running on the backend, some other bits (maybe in different language) running on the front, some code is waiting for response from a web service and some other code is waiting for the response of the user on the client side. You as a developer have to handle the synchronization.
We might actually say that today's web programming is distributed and asynchronous. As developers, we have to make the switch from the traditional sequential programming to the distributed and asynchronous code. The advent of cloud computing is forcing this transition.
Non-sequential, parallel or asynchronous code is hard to write, hard to debug and even harder to maintain. Write asynchronous code is challenging, however write asynchronous application in a transparent maintainable manner might feel impossible. Just think about the global variables which you have to sometimes create to hold the information about ‘current situation’, so that when a response from a web service arrives, you are able to decide and take the right actions.
So what are the tools which we will help us make this transition from sequential to asynchronous thinking and help us write readable and maintainable applications?
As developers, we can follow some conceptual model (for instance, the actors model) which will help us organize and architecture the program. To implement one of the models (patterns), we can use tested and well document code – existing library (for instance Akka). But sometimes even on one level lower, we can find the help in the programming language itself.
Models, Libraries and Languages
Libraries are and will be the principal tools to make developers life easier. There are several libraries available to help with the asynchronous, event-driven programming for many different languages. Akka, Node.JS, SignalR. But libraries themselves are build using languages. So the question is: What can languages bring to help make the life easier for developers in the age of cloud and distribution?
Modern languages have two characteristics:
Benefits of Functional Languages
Functional languages might be one of the helpers in the age of distributed computing. Some imperative languages are introducing functional aspects (for instance, C# has been heading that way since a long time), other ones designed from scratch are much more closer to "pure" functional style (Scala, F#). Let's first define some terms and possible benefits of functional programming. From my point of view (and I admit quite simplified point of view), there are four aspects of functional programming that are useful in everyday coding.
- Elimination of the "state" – the result of method is guaranteed no matter what the actual state is
- The ability to treat functions as first class citizens
- Presence of immutable distributable structures - mainly collections
- Lazy evaluation – since there is no state, we can postpone the execution of methods
Eliminating the State
It's hard to keep shared state in parallel systems written in imperative languages. Once we leave the save sequential execution of the language, we are never sure what are the values in the actual state. Callbacks might be executed about any time depending for example on network connection and the values in the main state could have changed a lot from the time of the "expected execution". Purely functional programming eliminates the outer "state of the system". The state has to be passed always locally. If we imagine such a language, all the methods defined would need an additional parameter in the signature to pass the state.
int computeTheTaxes (List<income> incomes, StateOfTheWorld state);</income>
That is really hard to imagine. So as it has been said in the discussion: pure functional programming is a lie. However we can keep this idea and apply it to some programming concerns. For instance, the immutability of the collections might be seen as application of “no current state.
Since the “current state” does not exists, the result of a function invoked with the same arguments should always be the same. This property is called “Referential transparency” and enables the lazy evaluation. So the elimination of the global state might be seen as the pre-condition for using other functional language features.
Function as a First Class Citizen
Another aspect of functional programming is the fact that functions become first class citizens. That means, that they can be passed as arguments to other functions (these are called higher order functions). This is an extremely powerful concept and you can do a lot with it. Functions can also be composed and the functional compositions applied to values. So instead of applying several consecutive functions on a collection of values, we can compose the resulting function and apply it at once. In C#, the LINQ uses a form of functional composition, which will be discussed later.
Lambdas & Closures
Collections in Functional Programming
In functional programming languages (the pure ones), collections are immutable. Instead of modification, a copy of the collection is returned on each operation which is performed on the collection. It is up to the designers of the language to force the compiler to reuse the maximum of the existing collection in order to lower the memory consummation. This allows the programmer to write the computation as a series of transformations over the collections. Moreover, thanks to lambdas and closures, these transformations may be defined on the fly. Here is a short example:
cars.Where(x=>x.Mark == ‘Citroen’).Select(x=>x.MaxSpeed);
This transformation will return an iterator of speeds of all
Citroens in the collection. Here, I am using C#/F# syntax, however almost the same code would compile in Scala.
The selector (“
Where”) and the mapper (“
Select”) both take as argument a function which takes an item of the collection. In the case of the selector, the function is a predicate which returns “
true” or “
false” in case of the mapper, the function just returns a new object. Thanks to lambdas, we can define both of them on the fly.
Language Integrated Data Queries
Lazy loading also comes from functional languages. Since the result of the function does not depend on the “state of the world”, it does not matter when we execute any given statement or computation. The designers of C# inspired themselves while creating LINQ. LINQ just enables the translation of the above presented chain of transformations to another domain specific language. Since the lazy loading is used, each computation is not performed separately, but rather a form of “functional composition” is used and the result is computed once it is needed. If the ‘
cars’ collection would be an abstraction for relational database table, the result would be translated into “select
maxSpeed from cars where mark=’
Citroen’. Instead of two queries (on for each function call).
Inside LINQ translates the C# query (the dotted pipeline of methods) into an expression tree. The tree is then analyzed (parsed) into domain specific language (such as SQL). Expression trees are a way to represent code (computation) as data in C#. So in order to develop and integrate the LINQ magic into the language, the language needs to support function as first class citizen and also has to be able to treat code as data.
Similar concept inspired some Scala developers and since Scala poses the necessary language features, we might see similar concepts in Scala also (ScalaQL).
Dynamic or Typed languages
What are the benefits of Dynamic or Strongly typed language? Let's look for the answer in everyday coding: Coding in dynamic language is at least at the beginning much faster than in a typed language. There is no need to declare the structure of the object before using it. No need to declare the type of the simple values nor objects. The type is just determined on the first assignment.
What works great for small programs might get problematic for larger ones. The biggest advantage of typed system in the language is the fact, that it is safer. It won't let you assign apples to oranges. It eliminates much of the errors such as looking for non-existing method of type.
The other advantage is the tooling which comes with the language. Auto-completion (code completion based on the knowledge of the type system) being example of one such a tool. The editor is able to propose you the correct types, methods, or properties. The types structure is used for analysis or later processing. For instance, documentation might be easily generated from the type system just by adding certain metadata.
Several languages offer compromise between the strongly typed (safe) and dynamic (flexible) world. In C#, we can use the
dynamic keyword to postpone the determination of the object type to runtime. DART offers optional type system. Optional type systems let us use the tooling, without polluting our lives with too much typing exercises. This comes in handy sometimes.
- Libraries will always be the core pieces to enable writing distributed software
- Language should be designed in a way to minimize the state and control 'purity' – functional languages are well studied and concepts coming from functional languages will become omnipresent in everyday programming.
- Type systems should be there once we need them and should get out of our way when we don’t.
The future might be interesting. Lately, I have been forced to write a lot of Java code and interact with some legacy code(<1.5) and besides the typing exercise, it does not provide me any benefits. It just bores me. Well, I am a fan of C#, because the authors of C# seem to search interesting features from other languages or concepts (closures, expression trees, dynamic typing, or later incorporating the asynchronous model directly to the language) and introduce them to the well established static typed, compiled (and for me well known) world.