Recently, I had to cope with a very peculiar production problem, which proved quite hard to tackle down and very easy to fix, as most of problems of this nature. The problem was manifesting sometimes on only one of our Java servers, and was leading to a sudden global slowdown of the whole JVM by at least 5-10 times. In the beginning, I was trying to find the needle in the haystack of concurrency, object creation, leaks e.t.c. However, there was nothing obvious in these places although the problem was looking like some kind of memory aggregation situation or deadlock situation, from which the JVM was never recovering.Ā
Days and months of investigation through recent and older logs, gc and heap analysis and massive optimizations did not seem to help and the devil was lurking anytime at our door, ready to hit with his unexpected visit. Only a restart would eventually help.Ā
One day, one of our brilliant engineers said that we should perhaps look into the internals of the JVM safepoints. As such, we activated at a first step the logging of these peculiar and strange safepoints through the addition of the
parameters. Have you ever had to look at this printout? Chances are that you didn't have to. For those of you though that have taken a glimpse into this hell of logging, you know what kind of pain it can cause. Even if somebody neglects the fact that there is no possibility to direct that logging to a special file and it HAS to be logged in the stdout file of your application, you can say that the printout could have been a bit better after 20 years of Java development. A safepoint log line consists of a timestamp - in seconds after the process startup - and an event. Even with that logging, we were not able to notice something useful and then we moved to the next step of activating the compilation trace of the JIT compiler of HotSpot. This polluted our stdout log file even more and not only that but the JIT events contain the timestamp not in the form of seconds but in the form of milliseconds since the process's startup time(-XX:+PrintCompilationĀ parameter).
However, we managed to track down a few hundreds of logs around the time of the problem appearing. Then we observed a very strange event saying something like thisĀ HandleFullCodeCache. More worrying was the fact that after that event, JIT seemed to only remove compiled code and turn already compiled methods into zombies. There was not a single optimization since then. As such, I decided to create a histogram with aggregations of 1 minute between optimization, deoptimizations and zombie events taken out from the JIT log. The result is depicted in the following picture.
The first orange spike in the histogram shows the initial optimizations done by JIT. These are good. In the beginning, a lot of optimizations, followed by zombies, are done until JIT reaches a certain mature state, where it decides which parts of the code are worthwhile staying optimized and which not. However, just before the middle of the day, we have two huge spikes, one for deoptimizations and one for zombies. Since the results are aggregated per second, we can say with certainty that these deoptimizations happened very suddenly. This was actually the time, at which we got the HandleFullCacheEvent. After that, no orange spikes can be seen, which would denote optimizations. In simple words that means that JVM runs completely without JIT, so it runs only in the old interpreted mode, which is of course very slow.Ā
Googling for possible causes, we landed over a notorious bug of the JDK, reported here http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8006952. It seems that JIT contains a bug, with which any further optimization is irreparably avoided once the code cache gets full. I managed to reproduce the problem on a simple test application by setting the code cache to a really small value, such that it is easy to get the event. By default, the cache is set to 96MB (as of JRE 1.7). After doubling the code cache (this can be done by setting the parameter -XX:ReservedCodeCacheSize), we never got into the problem again and the JIT histogram started looking like the following picture.Ā
Note: The bug is fixed in newer versions of Java. For those of you that still work with 1.7 though, remember that you can get into real trouble with this very strange and hard to find bug.Ā
Most of the Java developers usually tend to use either the newest GC or the one of their comfort zone. After obtaining some first empirical experiences with their applications, they may go up to just tune the size of the various heap parts and add some non-standard flags for printing out more detailed gc logging. Their confidence in letting the JVM (or better said the Oracle folks) choose most of the default parameters for the GC makes them to not give too much important on the implementation details of the various GCs. However, a basic understanding on the inner workings of each of the most important GCs is essential and keeping an overview of their differences can save a lot of production headaches. Moreover, the names of the GCs can actually mislead somebody. For example, how many developers know that using the ParallelOld GC is in most cases better than the ParallelGC?
Before going on with the explanation of the various GC types, it is important to say a few things on the configuration of the heap. The heap consists of three areas:
Young generation
Old generation
Perm generation
Most of the newly created objects reside in young generation. Those long-lived objects, which either have survived a configurable number of minor collections or do not fit anymore in the young generation move to the old generation. Permgen is a special heap area, which is occupied by class information of your code. However, young generation consists of three smaller areas. This is done in order to be able to support the generational and incremental features of modern garbage collectors. As such, young consists of:
Eden
Two survivor spaces
Eden is used as the birthplace of objects. When no more objects can fit into the eden space, then a minor collection will move the live objects into the "from" survivor area. Once the "from" survivor area approaches its limits, then all survivors are moved to the "to" survivor area and the "from" area gets empty completely. In this way, the two survivor areas exchange roles at every collection. Everytime one of the two is completely (or almost) empty. The objects that survive a certain number of movements (defined by -XX:MaxTenuringThreshold - default value 20 for JRE 1.7 and 15 for JRE 1.8)between the survivor areas get eventually moved to old generation.
The following tables appear in the Java Performance and Scalability of Henry H. Liu and are extremely useful in summarizing the basic characteristics of every major GC per heap area (I have only extended them to include Java's latest addition in the list of collectors, the G1 collector). The various GCs are classified based on the various implementation strategies/characteristics. Some of these strategies have been exploited in every language runtime that manages its memory by itself.
List of strategies
Serial - The strategy employe a single thread for doing its collections
Parallel - More than one threads are used for collecting objects
Copying - The strategy splits the heap in various areas and copies objects from one to another based on their age
Compacting - The strategy performs compaction in the heap for avoiding memory fragmentation
Concurrent - The collections or certain phases of the collection algorithm are performed in parallel to the running application
Incremental - The strategy does not finish the collection or a certain phase in one go but splits it into multiple parts, in order to give more live time to the application
Now that we have explained what each strategy does, we can go on categorizing the various GC algorithms according to their implemented strategies.
Table 1 Features with each collector for Young Generation GC
All of the below collectors have more or less the same algorithm for old generation collections (unless otherwise stated). The first step of this algorithm is to mark the surviving objects in the old generation. Then, it checks the heap from the front and leaves only the surviving ones behind (sweep). In the last step, the so called compaction is performed, where alive objects are allocated in one contiguous are in the old generation, so as to facilitate allocation of new objects and avoid fragmentation.
Serial collector (-XX:+UseSerialGC) The serial collector is one of the first ones that was introduced in the early JDKs and is supported as a legacy GC algorithm. It remains from the days, where applications were running on single core machines. As its names suggests, it uses just one thread and stop-the-world techniques to collect objects. Nowadays, it shouldn't be used at all and it is deactivated by default.
Parallel GC (-XX:+UseParallelGC)) The Parallel GC takes advantage of the modern multi-core machines and employs multiple threads for performing a GC. This algorithm is also called the throughput collector, since it tends to be the best choice for big heaps and tends to provide the best application time to gc time ratio. The Parallel GC though employs multiple threads only for the collections in young space. For collections in old generation, it still uses one single thread. The Parallel Old GC solves this problem. Although the high throughput characteristic of this algorithm sounds appealing, it may prove a disaster for responsive applications, which put a lot of value on their responsiveness. Such applications may desire a smaller throughput; in other words they may want to sacrifice some application time for better responsiveness or faster reaction times on user input.
Parallel Old GC (-XX:+UseParallelOldGC) As mentioned already, this collector employs the same techniques as the previous one but it uses multiple threads and a slightly different algorithm for the old generation. It goes through the steps mark, summary and compaction. The summary step is a bit more complicated than the traditional sweep of the other algorithms and identifies the surviving objects separately for those areas, where the mark phase has previously kicked-in. It is supported since JDK 5. The result is faster major collections for multicore environments. As a rule of thumb, somebody should prefer the Parallel Old GC when his system has at least 8 cores.
CMS GC (-XX:+UseConcMarkSweepGC) The Concurrent Mark-Sweep GC is a bit more complicated and consists in total of six different stages.
Initial mark - single threaded, stop the world small pause
Concurrent mark - single threaded, concurrent phase, which tries to mark objects while application is running
Precleaning phase - concurrent phase, which tries to minimize the time spent in the next, stop-the-wrold phase by checking the objects that are used by the application or stopped being used
Remark - multi-threaded, stop the world final marking
Concurrent sweep - concurrent, single-threaded phase in which CMS adds newly dead objects to its freelists
Reset phase - concurrent phase, in which CMS gets ready for the next GC cycle
CMS has also the following properties.
Due to its heavily concurrent nature, it needs more memory than other GCs
The compaction step is not done and it is performed only in case of a concurrent mode failure warning, in which case CMS falls back to the standard throughput collector.
CMS tries to do most of its work in parallel to the application. That's why this algorithm is best suited for application with high responsive needs. The application is stopped more often than in the default throughput collector but for much less time. This gives application users the desired responsiveness. However, CMS can be a pain if not configured correctly for your application needs. There are two major pains here: a) concurrent mode failure and b) promotion failure.
The fear of all CMS users is the so called concurrent mode failure, in which CMS falls back to the stop-the-world throughput (scavenge) collector. Even worse, in tha case, most applications will have such a full old generation, which will result unavoidably to a very big pause time (it is not unusualy for modern applications with many GBs of heap to have stop times in the range of 10ths of seconds). The concurrent mode failure happens when CMS concurrent phases cannot keep up with the rate of generation of new objects. Since CMS works in parallel to the application, it cannot ensure that it marks successfully all of the dead objects. As such, its collection rate is a bit slower than normally. On the other side, if your application has burst moments, in which it creates a lot of objects, then it can lead to the situation where CMS cannot catch up with it. In those cases, CMS will fall back to the stop-the-world GC in order to ensure safety of the application and that enough resources are freed before new objects get created. This is indicated with a concurrent mode failure.
For solving this pathological case, you may just need to add more memory, since as already stated CMs needs in general 20%-30% more than other collectors. If that doesn't help, then you may want to understand your application better and see how you can configure your CMS for better performance. In those cases, it usually helps to increase the eden and survivor spaces and delay the promotion of objects to the old generation by adding a bit tenuring threshold (-XX:MaxTenuringThreshold=20). This will ensure that objects do not get promoted to old generation so fast in the hope that CMS can catch up with the generation of objects.
The second problema ssociated sometimes to CMS is a failed promotion. A failed promotion happens when the collector fails to allocate enough contiguous space in your old generation. Since CMS does not perform compaction, fragmentation is unavoidable and if your application shows weird allocation and object size patterns, then you may end up in a situation where although there is enough total free space in your old generation, there is no contiguous space able to accomodate your object. In this case, a major stop-the-world collection together with compaction is enforced. In this case, only increasing the size of old generation or finding pathological allocation patterns in your application will save you from this.
G1 GC (-XX:+UseG1GC) G1 is the latest contribution of Oracle in the collection of it JVM GCs. It appeared in experimental version in JDK 1.6 and as stable in JDK 1.7. G1 tries to take a completely different approach than CMS by tackling its problems. In essence, it uses many of the CMS concepts but tries to combat the fragmentation problem. It does that by considering the whole heap as a unique, single area, as you can see from the image below.
It then divides that area into grids and assigns each grid to a specific heap area (e.g. young or old). The concepts of young and old generation are still used but in G1 they are managed in a more dynamic way. Another difference is that G1 is a compacting collector. G1 compacts sufficiently to completely avoid the use of fine-grained free lists for allocation, and instead relies on regions. This considerably simplifies parts of the collector, and mostly eliminates potential fragmentation issues. Also, G1 offers more predictable garbage collection pauses than the CMS collector, and allows users to specify desired pause targets. So far, I have gathered little experience with G1, since rumours have it to not have a great performance or to even lead JVMs to crashes. However, it seems that it is quite promising in combating some of the greatest problems of CMS. Those are; the object allocation rate or promotion varies significantly thoughout the lifetime of the application, most of the heap is occupied with live data or long pauses and compaction times.
Recently, I decided to try Spring as a framework for building a simple web services. So far, my endeavors with Spring have been covered by positive experiences, since this framework removes a lot of boilerplate code that somebody comes across, when developing enterprise applications.
The first thing I came across was the guides provided by Spring IO (http://spring.io/guides). Spring IO is a platform, which brings together all of Springās core APIs and frameworks into a well-versioned platform, which makes rapid-prototyping and application launching very easy. The guide for the RESTful service is actually so short and well-written that somebody gets immediately excited about the simplicity added by Spring. However, the guide states that everything is packed in an Ćber jar with a old-good Java application with a main method and an embedded Tomcat, configured for us directly by Spring. So Spring Boot takes care of most of the web configuration needed for creating a RESTful HTTP service. And this is actually the catch. All of the guides are biased towards using Spring Boot and the @Autoconfiguration annotation, which aim at reducing the initial effort for building an application. It may be perfect for proof-of-concepts and rapid prototyping but to be honest, I donāt see a way how this concept can be used in a productive environment.Ā
As such, I decided to take the a bit harder route of creating a simple RESTful service but configuring in my own Tomcat installation. This meant that I would have to configure my own web.xml file and create perhaps some extra Spring configurations. I just wanted to check two things: a) how long it would take for me to come up with the needed setup that somebody would have to engineer on a productive environment and b) whether the resulting solution would be as short as it is āevangelizedā by the Spring IO guides.Ā
Spring packages every web-related technology (like RESTful-,Web-services, Servlets, WebSockets, e.t.c.) in the Spring WEB package. This offers a framework that pretty much follows the common logic of every other well established MVC web framework out there, where there is a super/forward Servlet directing all requests to the appropriate controller. Spring allows the developer in this case to concentrate on his business logic and the implementation of his controllers. This is accomplished by using the @RequestMapping annotation, which maps certain HTTP requests to the desired functionality. All this is simple, seems intuitive to the average web developer and makes the developerās job really easy. However, every J2EE web application needs some appropriate configuration for defining the servlets, let alone other things like security, resource e.t.c. All this is defined in your web.xml configuration of your web application. You need to tell payout web container somehow, how to process incoming HTTP requests. But, the simple Spring IO guide does not show how to do this.Ā
Enough with talking though. This post should serve serveprimarily as a short guide for clarifying the hidden, magic configuration of Spring, which is though required when somebody needs to deploy his application in an existing, predefined application server like tomcat. Letās move then to the much anticipated coding part.
First we have our POJO, which should return the result of our operation, aka the result of our RESTful service. This essentially our model.
package com.ggkekas.web; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class Greeting { private final long id; private final String content; public Greeting(long id, String content) { this.id = id; this.content = content; } public long getId() { return id; } public String getContent() { return content; } }
The next snippet shows our simple controller, which is more or less the same with the one presented in the original Spring IO guides.
package com.ggkekas.web; import java.util.Date; import java.util.concurrent.TimeUnit; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class BookingController { @RequestMapping(value = "/bookingInfo", produces = MediaType.APPLICATION_JSON_VALUE) public BookingInfo bookingInfo( @RequestParam(value = "name", defaultValue = "Four Seasons") String name) { if ("ggkekas".equals(name)) { return new BookingInfo("Four Seasons", new Date( System.currentTimeMillis() + TimeUnit.DAYS.toMillis(5))); } else { return new BookingInfo("Grand Hyatt", new Date( System.currentTimeMillis() + TimeUnit.DAYS.toMillis(6))); } } }
Then we have the root-context.xml configuration, which is in essence required by Spring and contains our beans and their dependencies. It can be as minimal as just some lines, which define the packages to be scanned for auto-configuration by the Spring framework. And this is what we want here too.
Unfortunately, if you deploy the above files as part of a web application on a J2EE container, nothing will work. And here is where the search for the holy grail starts. It actually took me a couple of hours to find the solution by searching here and there and reading various guides. I will take you through my own mistakes and explain each one of them, in the hope that this will shed some light on the āauto configurationā magic of Spring.
Note: In the corrected examples below, the text in bold defines the extra configurations needed for the above code to work.
1st mistake - missing web.xml. In order for Tomcat container to create a web application out of our code, it needs a web.xml, which defines the necessary Servlets. Thankfully, Spring does a good job here by providing a Super Servlet, which is jsut a forwarding servlet, which forwards all requests to the appropriate controller (as I have explained above). The below web.xml file defines such a servlet and maps essentially all our requests to that servlet. Note the definition of the locationĀ of our root context. This is how Spring configures the generic DispatcherServlet in order to know, which classes define our controllers and for which requests they are configured.
Here I have defined only one request (bookingInfo) but essentially any other request could be mapped to the same servlet, provided that no sophisticated servlet handling is needed by your application. The DispathcerServlet of Spring will then forward that request further to our Controller. In this way, we have indeed a powerful MVC framework, where the developer does not need to care about container-specific technologies, like Servlets (although some basic understanding of the inner workings is definitely a prerequisite) , but can concentrate entirely on his controller business logic. The above web.xml is indeed small and seems intuitive but Spring IO tutorials do not explain that. It took me actually some time to work this out, after hurting my brain seriously though Spring Web documentation.
2nd mistake - missing @ResponseBody annotation and produces attribute in @RequestMapping annotation. Spring WEB is quite powerful and has excellent customizing capabilities in its MVC framework. But it is cumbersome too. As such, every controllerās response needs to be mapped to a view. In this way, Spring can decouple the body of the controller from any view-specific technology. The configuration needs as such to know how to map the requests to specific views. When you build a WEB service though, you are not interested at all in view technologies. You just want to be able to return the response of your service in an appropriate format, e.g. Json or XML. This is what the @ResponseBody annotation does for us. It tells Spring to treat the result of our controller as the body of our response directly. In this way Spring knows that it shouldnāt apply any view and just returns your result as is back. Secondly, we need to tell Spring in which format we want our result to be returned. This is accomplished by theĀ produces = MediaType.<value> attribute-value pair.
package com.ggkekas.web; import java.util.concurrent.atomic.AtomicLong; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @RequestMapping(value = "/greeting", produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public Greeting greeting( @RequestParam(value = "name", defaultValue = "World") String name) { return new Greeting(counter.incrementAndGet(), String.format(template, name)); } }
Even after all the above, you still need to transform the result from a plain Java object to some serialized representation. This brings us to the next important points and mistakes I have done. Notice that in my example I have used Json as the format of my serviceās responses.
3rd mistake - configure Json transformations. Since, we need Spring to be able to transform the responses of our messages to a Json string, we need to configure the so called mapping. The following configuration does this for you. You donāt need to code anything for it actually, since Spring allows you to embed your transformation of choice into its objects. And there is already a Json transformation ready for us through the Jackson library. The xml below show the "correct" version of the root-context.xml file, which we have defined above. This time, it contains though the necessary Json message converters. We would have done the same, had we needed XML as the response of ourt RESTful service. In the latter case, we would have defined the class org.springframework.http.converter.xml.MarshallingHttpMessageConverter as the appropriate for converting our reponses to valid XMLs.
4th mistake - configure Jackson libraries correctly. As you have understood from above, we need the Jackson libraries in our classpath as well. If you have included the Spring boot starter library already,mother your classpath will be filled with all the dependent libraries, which include some from Jackson as well. This can be confusing since you may believe that you have everything you need for the Jackson library to work effectively. However, for the transformations to work correctly, you will need to include the jackson-core-asl and jackson-mapping-asl as well, which follow a completely different release plan and have different versions from the default Jackson libraries. The difference lies also in the licensing strategy, where the asl libraries use LPGL whereas ten other ones the.
After all that, everything should be ready. Package your application in a WAR file with your favourite build management system and deploy it on your favourite container. Try calling it with http://host:port/bookingInfo or http://host:port/bookingInfo?name=ggkekas and you will get JSON output directly on your browser.
Note: You can see that the above examples do not contain a Starting class with a main method, as this is defined in the Spring IO guides. This is because we wanted to do exactly this and avoid the AutoConfiguration magic of Spring by provisioning a typical J2EE web application on a container.
Conclusions
With regards to the code required to deploy a full-blown RESTful service, Spring does quite a good job and indeed removes a lot of burden and boilerplate code from the developer. Even in the "corrected" examples, most of the missing parts were just configurations and annotations. On the downside, a developer does need to understand the inner workings and some details of the Spring framework, even if he just needs to deploy a very simple Web Service like the above. For more complicated staff, you will have to "hurt your brain" literally.
However, Spring IO together with its new Starter package does a really good job in providing a rapid prototyping framework, which can excellently be used in proof of concepts. In that case, Spring does most of the above job for you. It is only when you will have then to create a production-ready system, that you will have to deal with some of the details of Spring.
Who should use functional programming? A philosophical discussion.
As many of you may have already seen, there is currently a hype in software development about dynamic and/or functional programming languages. As usual, a hype is given birth either by needs that are by definition artificially generated (like it happens with the latest consumer products, see iPhone and tablets) or by some need that cannot be satisfied by the current technology. However, software development seems to be generating hypes quite often that do not follow in any of the previous categories. And this is because... software experts tend to re-invent the wheel. Now why am I saying that? Just because functional programming has been existing since the early 30s. Damn right!!! In order though to be more honest, I can correct this by saying that it has its roots back to the 30s of the previous century and as a matter of fact to lambda calculus. But before I say more on this, I will try to provide a bit of historical data.
Functional programming started becoming a hype - or a trend if you want - after people discovered that object oriented programming could not solve entirely their problems. In fact, OOP started creating new problems that were not existing before. The model on which OOP has been built, appeared not to be sufficient or even correct sometimes. Programmers eventually understood, that just because OOP makes it easier for the human brain to conceive software models and architectures, the same does not hold for the computer themselves. OOP started at early 70s of the previous century with Smalltalk (although example of other OOPs existed even before) and received wide acceptance in the early 90s with the introduction of C++, again as a result of a hype (now you can see that history cycles exist everywhere). OOP promised almost a solution to almost everything. Experience and many billions of lines of code have shown that these promises have failed (the reasons for that could be a separate post itself). Therefore, programmers and computer science enthusiasts starting thinking on the next evolution. And that's where we are. All of the sudden, functional and dynamic programming languages came out of nowhere to provide the light in the darkness of OOP. Needless to say that most of the "most modern" functional programming languages out there are not purely functional, in the sense that they tried to just provide functional decoration on OOP (like it happens with Scala, Ruby, Groovy e.t.c.).
Now, the idea of functional programming is not bad at all. Actually functional programming tries to solve one of the most fundamental problems in software development, which is... guess guess guess... determinism. A function is thought to have no side effects at all (or at least minimal and trackable ones) on the state of a program. That means, a function has an input and an output and calling that function multiple times with exactly the same input will give exactly the same output, without affecting the environment or state of the program in general. This is called idempotence and it sounds actually pretty good. However, as I have already said, it is very oftern that functional programming languages Ā - and especially the newer ones - break this rule. And they do that in order to 1) be easier to be used and as such understood by the established developing communnity and 2) provide a somehow "hybrid" functionality by offering some of the traditional candys of OOP or imperative programming - shit... wait a minute, haven't I just metioned some lines above that OOP turned out to be bad? Oh my god!!! What should I do now? What should I use? Everything seems to be bad. Well not exactly. And this is where I would like to conclude.
Nothing is bad and nothing is perfect either. Somebody should make right use of the rights tools for the right application or needs. Typical applications that can be efficiently and elegantly be coded by functional programming languages include but are not limited to text processing, pattern matching and applications with heavy mathematical and/or algorithimic operations. Business applications like an online store, an invoice system, a customer care system e.t.c. benefit from the inherent model of the OOP because that model can describe in a natural way their business domain.
Let's take as an example the quicksort algorithm. The following code snippet shows the implementation of quicksort in Groovy (my favourite functional programming language).
Difficult to believe? Try it yourself. You can see how the semantics of the algorithm can be translated intuitively through the expressional power of the language. This is attributed solely to the mathematical nature of the expressions in functional programming languages and their excellent built-in processing of data structures like lists. Lists and trees are an integral part of almost every functional programming language and do not come as part of an extra library or package like in most OOP languages. Below is the mergesort algorithm again in groovy.
static def merge(l, r) { def less = l.findAll { it < r[0] } def li = l.size() - (l.size() - less.size()) def greater = r.findAll { it < l[li] } def ri = r.size() - (r.size() - greater.size()) def result = less + greater; if(li < l.size() && ri < r.size()) { result += merge(l[li..-1], r[ri..-1]) } else if(li < l.size()) { result += l[li..-1] } else if(ri < r.size()) { result += r[ri..-1] } result } static def mergesort(list) { if (!list || list.size() <= 1) return list def left = list[0..(list.size()/2 - 1)] def right = list[(list.size()/2)..-1] left = mergesort(left) right = mergesort(right) merge(left, right) }
Now compare the above examples with their equivalents in C, Basic or even Java. Obviously the implementations in those languages are much more verbose and less elegant.
Some may accuse the functional programming languages of producing programs, which are unreadable, unmaintanable and hard to debug. Now to a certain degree, I agree with them but to be honest I strongly believe that most of these accusations come usually from the fact that these people have never programmed in a functional way seriously. As all of the things, this is also a habbit that deserves its time and it can be earned. And I'm sure that once there, it is also difficult to go back to any real OO or imperative programming language. On the other hand, it is difficult for me to imagine a huge full-blown financial application with millions of transactions going on every minute and complicated bussines models and rules to be written in a pure functional programming language. In that case I agree that somebody can get easily lost into the abyss of tail recursion and kilometers long stacks.
So, what is the conclusion? The answers is: the right tool should be used for the right job. If your current application is a small to mid sized one with an algorithmic or mathematical inclination, then a functional programming language is exactly what you were looking for. If we are talking though about a multi-million server side business application, that needs to model quite some complicated business rules, then a more traditional and widely accepted programming language like Java may serve your needs. Again these rules need not be followed blindly. There are many people/bussinesses, who form the exceptions in the rule, that could give millions of arguments for their opposite decisions. However, practical experience has shown that the rule of thumb lies somewhere there. Important is to be open to new ideas and technologies, to evaluate them regularly for your specific needs and not to worship a specific programming language.