Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Comparing JVM languages

1,888 views

Published on

Comparing JVM languages
Java 8,Scala,Groovy,Clojure,Kotlin,Ceylon

Published in: Technology
  • Some of the disadvantanges presented in the Clojure part seem are claims that require support. If by variables you mean handlers with a value that can change over time, there are indeed variables in Clojure: atom, agents, refs. However, if you mean variables in the tradicional sense (thread-unsafe pointers to mutable objects), I don’t see why that should be an advantage or a disadvantage per se. There are many articles out there explaining the benefits of enforcing immutability, and you don’t really need them when coding in Clojure. Also, there are inheritance concepts: check protocols and multimethods, and the functions for reification and type extension (reify, extend-type, derive). There are many alternatives available, not everything has to emulate the somehow rigid and limited class system in Java. But the most surprising is “requires a new way to solve problems”. How can that possibly a disadvantage?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Comparing JVM languages

  1. 1. Comparing JVM languages José Manuel Ortega Candel | @jmortegac 26-27 June 2015
  2. 2. INDEX Functional programming and closures2 Language classification1 Java 8 new features3 Scala, Groovy, Kotlin, Clojure and Ceylon4 Advantages and drawbacks5 Comparing features6
  3. 3. Language classification FUNCTIONAL NOT FUNCTIONAL STATIC DYNAMIC
  4. 4. Functional programming •Data structure persistent and immutable •Closures •Higher order functions •Pass functions to functions as parameter •Create functions within functions •Return functions from other functions •Functions as first class values
  5. 5. Closures •Reduce amount of “boiler plate” code •Eliminate the boilerplate code of anonymous inner classes •Allow the use of idiomatic functional programming code •Allow the creation of custom control structures •In Java 8 closures are just syntactic sugar for defining anonymous inner classes
  6. 6. Java 8 new features  Lambda expressions  Closures  Streams  Enhaced collections  Processing data in parallel  Pattern Filter-Map-Reduce  Annotations types  Methods References (String s)-> System.out.println(s) System.out::println
  7. 7. Streams
  8. 8. Filter-Map-Reduce Book book=new Book(123455,"Beginning Java 8"); Book book1=new Book(3232332,"Java 8 Advanced"); Book book2=new Book(43434343,"Groovy Advanced"); List<Book> books=Arrays.asList(book,book1,book2); Predicate<Book> condition=(it) -> it.getTitle().contains("Java"); String jsonFormat=books .stream() .filter(condition) .map(Book::toJSON) .collect(Collectors.joining(", ")); [{isbn:123455,title:'Beginning Java 8'}, {isbn:3232332,title:'Java 8 Advanced'}]
  9. 9. Parallel Streams List myList = .. Stream stream = myList.stream(); Stream parallelStream = myList.parallelStream(); Stream parallel = stream.parallel(); •Streams are sequential by default •With Parallel you can optimize number of threads •More efficient with bigger collections
  10. 10. Sequential vs Parallel books.parallelStream().filter((it) -> it.getTitle().contains("Java")). forEach((it)->System.out.println(it+" "+Thread.currentThread())); Book{isbn=3232332, title='Java 8 Advanced'} Thread[main,5,main] Book{isbn=123455, title='Beginning Java 8'} Thread[main,5,main] books.stream().filter((it) -> it.getTitle().contains("Java")). forEach((it)->System.out.println(it+" "+Thread.currentThread())); Book{isbn=3232332, title='Java 8 Advanced'} Thread[main,5,main] Book{isbn=123455, title='Beginning Java 8'} Thread[ForkJoinPool.commonPool- worker-2,5,main]
  11. 11. Scala Combines the paradigms of functional programming and object-oriented Statically typed No types primitives like Java Everything is an Object, including functions Performance equivalent to Java
  12. 12. Scala features Pattern matching Closures,lambdas,anonymous functions Traits and mixins for multiple inheritance Operator overloading Support Data Paralell like Java 8 Concurrency models(Akka,actors) Inmutable and mutable data structures
  13. 13. Scala examples def factorial(n : Int): Int = n match{ case 0 => 1 case x if x >0 => factorial(n-1) *n } val languages = List("Scala", "Groovy", "Clojure", "Kotlin") val headList = languages.head // Scala val tailList = languages.tail; //List( "Groovy", "Clojure", "Kotlin") val empty = languages.isEmpty //false def orderList(xs: List[String]): List[String] = if (xs.isEmpty) Nil else insert(xs.head, isort(xs.tail) @BeanProperty Annotation which is read by the Scala compiler to generate getter and setter class Bean { @scala.reflect.BeanProperty var name: String } val someNumbers = List(-11, -10, -5, 0, 5, 10) someNumbers.filter((x) => x > 0) someNumbers.filter(_ > 0) List[Int] = List(5, 10) scala.collection.immutable
  14. 14. Inmutability in Scala scala> var languages = Set("Scala", "Kotlin") languages : scala.collection.immutable.Set[java.lang.String] = Set(Scala, Kotlin) scala> languages += "Clojure" scala> languages res: scala.collection.immutable.Set[java.lang.String] = Set(Scala, Kotlin, Clojure) scala> languages -= " Kotlin " scala> languages res: scala.collection.immutable.Set[java.lang.String] = Set(Scala, Clojure) • You can add elements in a inmutable collection
  15. 15. Java 8 vs Scala
  16. 16. Java 8 vs Scala streams bookList.parallelStream().filter(b -> b.title.equals(”Clojure”)) .collect(Collectors.toList()) bookList.par.filter(_.title == “Clojure”) bookList.par.filter( b =>b.title == “Clojure”) val bookFilter = bookList.par.filter(_.title.contains("Groovy")) .foreach{i=> println(Thread.currentThread) } Thread[ForkJoinPool-1-worker-3,5,main] Thread[ForkJoinPool-1-worker-5,5,main]
  17. 17. Kotlin Object-oriented with functional elements Developed by JetBrains Statically typed like Scala Smarts casts Elvis operator(?) for checking null like Groovy Type inference 100 % interoperability with Java
  18. 18. Kotlin syntax & rules Inmutable/Mutable variables No new keyword for create objects No primitive types No static members Primary constructors No fields, just properties By default, all classes are final
  19. 19. Kotlin Smart casts Maps val languages = mapOf("Java" to "Gosling", "Scala" to "Odersky","Groovy" to "Strachan") for((language,author) in languages){ println("$author made $language") } fun eval(e: Expr): Double = when (e) { is Num-> e.value.toDouble() is Sum -> eval(e.left) + eval(e.right) else -> throw IllegalArgumentException("Unknown expression") } val mapLanguages = hashMapOf<String, String>() mapLanguages.put("Java", "Gosling") mapLanguages.put("Scala", "Odersky") for ((key, value) in mapLanguages) { println("key = $key, value = $value") } fun patternMatching(x:Any) { when (x) { is Int -> print(x) is List<*> ->{print(x.count())} is String -> print(x.length()) !is Number -> print("Not even a number") else -> print("can't do anything") } }
  20. 20. Kotlin Operator overloading Default & name arguments println(product(2)) println(product(2,3)) println(format(text="kotlin")) println(format(text="kotlin", upperCase= true)) 2 6 kotlin KOTLIN fun product(a:Int,b:Int=1):Int{ return a.times(b); } fun format(text:String,upperCase:Boolean= false):String { return if (upperCase) text.toUpperCase() else text }
  21. 21. Kotlin Iterators fun iterateOverCollection(collection: Collection<Int>) { for (element in collection) {} } fun iterateOverString() { for (c in "abcd") {} "abcd".iterator() } fun iteratingOverMap(map: Map<Int, String>) { for ((key, value) in map) {} }
  22. 22. Kotlin Data classes  Data annotation changes behavior of hashCode, equals, toString functions public class Book (var isbn:Int, var title:String) fun main(args: Array<String>) { //Books val book = Book(123456, "Beginning Kotlin") val book2 = Book(123456, "Beginning Kotlin") println(book); println(book.hashCode()); println(book2.hashCode()); println(book.equals(book2)); if (book == book2) println("Same Book"); if (book != book2) println("Diferent Book"); } books.Book@2a84aee7 713338599 168423058 false Diferent Book data public class Book (var isbn:Int, var title:String) fun main(args: Array<String>) { //Books val book = Book(123456, "Beginning Kotlin") val book2 = Book(123456, "Beginning Kotlin") println(book); println(book.hashCode()); println(book2.hashCode()); println(book.equals(book2)); if (book == book2) println("Same Book"); if (book != book2) println("Diferent Book"); Book(isbn=123456, title=Beginning Kotlin) 1848623012 1848623012 true Same Book
  23. 23. Kotlin Classes constructor • It is not necessary define a constructor like in Java • You can use the class definition to pass the properties and default values data public class Book (var isbn:Int, var title:String="My Default Book") fun main(args: Array<String>) { val book = Book(123456) println(book.title); }
  24. 24. Kotlin Higher order functions • Lambda expressions • it implicit parameter val filter = books filter({ b: Book -> b.title.contains(“Kotlin") }) val book = Book(123455, "Beginning Java 8") val book1 = Book(3232332, "Java 8 Advanced") val book2 = Book(43434343, “Kotlin Advanced") val books = Arrays.asList(book, book1,book2) Filter[Book(isbn=43434343, title=Kotlin Advanced)] val filter = books filter({ it.contains(“Kotlin") })
  25. 25. Kotlin collections val listInmutable =listOf("Java","Kotlin","Groovy","Scala") val listMutable=ArrayList<String>() listMutable.add("Java") listMutable.add("Kotlin")
  26. 26. Kotlin vs Java list iterate for ((index,element) in list.withIndex()) { println(“$index$element”); } int index=0 for (String element:list) { System.out.println(index+””+element); index++ }
  27. 27. Kotlin smart cast vs Java if (expr is Number) { println(expr.getValue()); //expr is automatically cast to Number } if (expr instanceof Number) { System.out.println((Number)expr).getValue()); }
  28. 28. Kotlin vs Java types inference val longJavaClassName = LongJavaClassName() LongJavaClassName longJavaClassName =new LongJavaClassName()
  29. 29. Groovy  Java supercharged(syntactic sugar)  Object oriented and dynamic language  Optional typing like Scala and Kotlin  All type checking is at run time  It reduces Java boilerplate code including no semi colons, no getters/setters, type inference, null safety, elvis operators
  30. 30. Groovy  Closures with lambda or it implicit parameter def upper = { s-> s.toUpperCase()} def upper = {it.toUpperCase()}  Collections def languages = ["Java":”James Gosling”, "Scala":”Martin Odersky”, "Clojure":”Rich Hickey”, "Ceylon":”Gavin King”, "Groovy":”James Strachan”] languages.each { entry -> println} languages.each { k,v -> println}  Methods as closures with .& operator def object object = 2 object = true object = [1,2,3]  Optional typing
  31. 31. Groovy  @Memoized annotation  @Inmutable annotation @Memoized def BigInteger fibRecursiveMemoized(n) { if (n<2) return 1 else return fibRecursiveMemoized(n-1) + fibRecursiveMemoized(n-2) } TimeIt.code{println(fibonnaci.fibRecursive(40))} TimeIt.code{println(fibonnaci.fibRecursiveMemoized(40))} 165580141 Time taken 42.083165464 seconds 165580141 Time taken 0.061408655 seconds //with memoize import groovy.transform.Immutable @Immutable class Language { String title; Long isbn; } def l1 = new Language (title:'Beginning Groovy',isbn:100) def l2 = new Language (title:'Groovy advanced',isbn:200) l1.title = 'Groovy for dummies' // Should fail with groovy.lang.ReadOnlyPropertyException
  32. 32. Groovy  Time execution class TimeIt { def static code(codeBlock){ def start = System.nanoTime() try{ codeBlock() }finally{ def end = System.nanoTime() println "Time taken ${(end-start)/1e9} seconds" } } }
  33. 33. Groovy  Parsing and creating XML
  34. 34. Groovy Advantages Disadvantages Java Collections Non-lazy evaluation Closures (like Lambda expressions in Java 8) It's performance isn't great (but it's improving) Filter – Map – Reduce in streams Easy interoperability with Java Static and dynamic typing @TypeChecked @CompileStatic Metaprogramming
  35. 35. Java Groovy def languages = ["Java", "Scala", "Clojure", "Kotlin", "Ceylon","Groovy"] def filter = languages.stream() .findAll { it.size()>5 } .collect { it.toUpperCase()} .first() List<String> languages = Arrays.asList("Java", "Scala", "Clojure", "Kotlin","Ceylon","Groovy"); Optional<String> bookFiltered = languages.stream() .filter(s -> s.length()>5 ) .map(s -> s.toUpperCase()) .findFirst();
  36. 36. Clojure Pure functional language JVM language based in Lisp Not Object Oriented Dynamically typed All data structure including list ,vectors, maps, sequences, collections, are inmutable and fully persistent The main data structure is the list The key is the use of High Order Functions
  37. 37. Clojure Alternate implementations in functions (defn square-or-multiply "squares a single argument, multiplies two arguments" ([] 0) # without arguments ([x] (* x x)) # one argument ([x y] (* x y))) # two arguments List operations (first '(:scala :groovy :clojure))return scala (rest '(:scala :groovy :clojure))return all elements excepts the first (cons :kotlin '(:scala :groovy :clojure))add kotlin to head in the list
  38. 38. Clojure Sequences (defn count-down [n] (if (<= n 0) '(0) (cons n (lazy-seq (count-down (dec n)))))) Filter – Map - Reduce (filter integer? [1 2.71 3.14 5 42]) => (1 5 42) (map (fn [n] (* n n)) [1 2 3 4 5]) => (1 4 9 16 25) (reduce + [1 2 3 4]) => 10 user=> (count-down 8) (8 7 6 5 4 3 2 1 0)
  39. 39. Clojure Records (defrecord Book [isbn title]) (def bookList [(Book. 432423 "Clojure")(Book. 534243 "Kotlin") (Book. 432424 "Groovy")]) (println "count bookList "(count bookList)) (dorun (for [item bookList] (println (:title item)(:isbn item)))) (println (filter #(= "Clojure" (get % :title)) bookList)) count bookList 3 Clojure 432423 Kotlin 534243 Groovy 432424 (#user.Book{:isbn 432423, :title Clojure}) (defrecord Object [prop1 propn])
  40. 40. Clojure Recursive functions with trampoline and memoize (defn factorial ([x] (trampoline (factorial (dec x) x))) ([x a] (if (<= x 1) a #(factorial (dec x) (*' x a))))) (def fibonaci (memoize (fn [n] (if (< n 2) n (+ (fibonaci (dec n)) (fibonaci (dec (dec n)))))))) (time(println (factorial 20))) (time(println (fibonaci 30))) "Elapsed time: 0.692549 msecs" 832040 "Elapsed time: 1.263175 msecs" "Elapsed time: 24.574218 msecs" 832040 "Elapsed time: 118.678434 msecs" 2432902008176640000
  41. 41. Clojure Advantages Disadvantages Very simple Not Object oriented The language has little elements compared with other languages Difficult to decipher stacktraces and errors Excellent concurrency support Requires a new way to solve problems Easy interoperability with java There are no variables Powerful language features (macros,protocols) Thre is no inheritance concept Clojure programs run very quickly No mecanism for avoid NullPointerException JVM is highly optimized
  42. 42. Clojure vs Java Code reducing (defn blank [str] (every#? (Character/isWhitespace %) str)) public class StringUtils { public static boolean isBlank(String str) { int strLen; if (str == null || (strLen = str.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { if ((Character.isWhitespace(str.charAt(i)))) { return true; }} return false; }}
  43. 43. Ceylon Is very similar to Java, reducing verbosity Object oriented with functional features Strong static typing with type inference Explicit module system  Comparing with Java  Eliminates static, public, protected, private  Add new modifiers like variable, shared, local  Shared is used for make visibility outside in other modules(like public in Java)  Arrays are replaced by sequences
  44. 44. Ceylon Sequence languages = Sequence("Java", "Groovy","Clojure","Kotlin"); //Check if the sequence is empty if(nonempty languages){ String? firstElementValue = languages.value(1); String? firstElement = languages.first; } Sequence more = join(languages,Sequence("Ceylon","Scala")); Sequences Generics & Union types Iterable<String|Boolean> list = {“string”,true,false} ArrayList<Integer|Float> list = ArrayList<Integer|Float> (1, 2, 1.0, 2.0)
  45. 45. Comparing featues •Reducing verbosity •Variables definition •Null-safety •Optional types •Lambdas •Pattern matching •Traits •Java interoperability
  46. 46. Reducing Java verbosity Reducing Boilerplate Code class MyClass{ private int index; private String name; public MyClass(int index, String name){ this.index = index; this.name = name; } } case class MyClass(index: Int, name: String) public class MyClass(var index:Integer,var name:String){ } class MyClass { int index String name } class MyClass(index,name) { shared Integer index; shared String name; } (defstruct MyClass :index :name)
  47. 47. Variables definition • In Kotlin and Scala you can define 2 types • var myMutableVariable • val myInmutableVariable • In Ceylon we have variable modifier for mutable variable Integer count=0; count++; //OK Integer count=0; count++;//ERROR val name: Type = initializer // final “variable” var name: Type = initializer // mutable variable
  48. 48. Null-safety •Operator ? used in Kotlin,Groovy,Ceylon •Avoid NullPointerException var b : String? = "b" b = null // valid null assignment val l = b?.length() Elvis Operator(?:) and Safe navigation (?.) def displayName = user.name ? user.name : "Anonymous“ def streetName = user?.address?.street String? argument = process.arguments.first; if (exists argument) { //checking null values }
  49. 49. Optional Types Optional<Object> optional = findObject(id); //check if Optional object has value if (optional.isPresent()) { System.out.println(optional.get()); } Type safe Option[T] in Scala New class java.util.Optional
  50. 50. Optional Type in Scala private val languages = Map(1 -> Language(1, "Java", Some("Gosling")), 2 -> Language(2, "Scala", Some("Odersky"))) def findById(id: Int): Option[Language] = languages.get(id) def findAll = languages.values def main(args: Array[String]) { val language = Optional.findById(1) if (language.isDefined) { println(language.get.name) } } val language = Optional.findById(3) None
  51. 51. Optional Type in Scala val languages =Map("Java"->"Gosling","Scala"->"Odersky") languages:scala.collection.immutale.Map[java.lang.String,java.lang.String] languages get "Scala" Option[java.lang.String] = Some(Odersky) languages get "Groovy" Option[java.lang.String] = None
  52. 52. Lambdas in streams List<Book> books=Arrays.asList(book,book1,book2); Optional<String> bookFiltered = books.stream() .filter(b -> b.getTitle().contains("Groovy")) .map(b -> b.getTitle().toUpperCase()) .findFirst(); val books = Arrays.asList(book,book1,book2) val bookFiltered : String? = books.sequence() .filter { b->b.title.contains("Groovy")} .map { b->b.title.toUpperCase() } .firstOrNull() def bookfilter= books. findAll { b->b.title.contains(" Groovy ") } .sort { b->b.isbn } .collect { b->b.title.toUpperCase() } .find() val bookList = List(book1,book2,book3); val bookFilter = bookList .filter(b=>b.title.contains("Groovy")) .map(book => book.title)
  53. 53. Pattern matching • Is a generalization of Java switch/case def generalSize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size case _ => -1 } scala> generalSize("abc") res: Int = 3 scala> generalSize(Map(1 -> 'a', 2 -> 'b')) res: Int = 2 fun fib(n: Int): Int { return when (n) { 1, 2 -> 1 else -> fib(n - 1) + fib(n - 2) } } fun patternMatching(x:Any) { when (x) { is Int -> print(x) is List<*> ->{print(x.size())} is String -> print("String") !is Number -> print("Not even a number") else -> print("can't do anything") } }
  54. 54. Traits vs Java interfaces Traits allow declare method definitions A trait is an interface with an implementation(behavior and/or state) Traits support “multiple inheritance” One class can inherit from many traits
  55. 55. Traits.Code in interfaces trait Publication { int number def getNumberPublication(){ return (number) } } trait Location { String description def getInfoPublication(){ return (description) } } class MyBook implements Publication,Location{ Integer isbn String title def getInfoBook(){ return (isbn+ " "+title) } } trait Publication { def description() = println(“Description”) } class Book(var isbn:Int,var title:String) extends Publication{ def getInfoBook(){ return (isbn+ " "+title) } } trait Publication{ var number:String fun getNumberPublication(){ println(number) } } class Book:Publication { }
  56. 56. Java interoperability object list extends App { import java.util.ArrayList var list : ArrayList[String] = new ArrayList[String]() list.add("hello") list.add("world") println(list) } import collection.JavaConversions._ java.util.Arrays.asList(1,2,3). foreach(i =>println(i)) java.util.Arrays.asList(1,2,3). filter(_ % 2 ==0) fun list(source: List<Int>):ArrayList<Int> { val list = ArrayList<Int>() for (item in source) list.add(item) for (i in 0..source.size() - 1) list[i] = source[i] return list } (ns clojure-http-server.core (:require [clojure.string]) (:import (java.net ServerSocket SocketException) (java.util Date) (java.io PrintWriter BufferedReader InputStreamReader BufferedOutputStream)))
  57. 57. Java interoperability import scala.beans.BeanProperty class Language( @BeanProperty var name: String, @BeanProperty var author: String) { } public static void main(String[] args) { Language l1 = new Language(“Java",”Gosling”); Language l2 = new Language(“Scala",”Odersky”); ArrayList<Language> list = new ArrayList<>(); list.add(l1); list.add(l2); }
  58. 58. List Partitions • We can use partition over a list for obtain 2 lists scala> List(1, 2, 3, 4, 5) partition (_ % 2 == 0) res: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5)) // partition the values 1 to 10 into a pair of lists of even and odd numbers assertEquals(Pair(listOf(2, 4, 6, 8, 10), listOf(1, 3, 5, 7, 9)), (1..10) .partition{ it % 2 == 0 })
  59. 59. Read file and print lines import scala.io.Source for (line <- Source.fromFile(“file.txt”).getLines()) println(line.length +" "+ line) import java.nio.file.*; import java.util.stream.Stream; Path dict = Paths.get(“file.txt”); Stream<String> linesStream = Files.lines(dict); linesStream.forEach(System.out::println); val dict = Paths.get(“file.txt") val linesStream = Files.lines(dict) val lines = linesStream.forEach{println(it)};
  60. 60. Read file and print lines (defn read-file [file] (with-open [rdr (clojure.java.io/reader file)] (doseq [line (line-seq rdr)] (println line)))) (read-file (file “file.txt")) Files.lines(Paths.get(“file.txt")).forEach{println it};
  61. 61. Performance in recursion (defn factorial ([x] (trampoline (factorial (dec x) x))) ([x a] (if (<= x 1) a #(factorial (dec x) (*' x a))))) @TailRecursive def fact(BigInteger n,acumulator = 1G) { if (n <2){ acumulator }else{ fact(n-1,n*acumulator) } } @scala.annotation.tailrec def factorialrec(fact:BigInt,number:BigInt=1):BigInt = { if (fact<2) return number else factorialrec(fact-1,fact*number) }
  62. 62. Comparing features Higher Order Functions Mixin/traits Pattern matching Implicit casts Nullables / Optionals Modules Promote inmutability Lambdas
  63. 63. Palindrome (use 'clojure.java.io) (defn palindrome? [s] (let [sl (.toLowerCase s)] (= sl (apply str (reverse sl))))) (defn find-palindromes[s] (filter palindrome? (clojure.string/split s #" "))) fun isPalindrome(s : String) : Boolean { return s== s.reverse() } fun findPalindrome(s:List<String>): List<String>{ return s.filter {isPalindrome(it)}.map {it} } def isPalindrome(x:String) = x==x.reverse def findPalindrome(s:Seq[String]) = s find isPalindrome def isPalindrome(s) { def s1 = s.toLowerCase() s1 == s1.reverse() } def findAllPalindrome(list) { list.findAll{isPalindrome(it)} }
  64. 64. Conclusions Full interoperability with Java Reducing Java verbosity These languages allow developer be more productive comparing with java Pragmatic Programmer advice: When you learn a new language, you learn a new way to think.
  65. 65. Resources SCALA • http://www.scala-lang.org • http://www.scalakoans.org • http://scala-ide.org • https://www.coursera.org/course/progfun KOTLIN • http://kotlinlang.org • http://kotlin.jetbrains.org • http://kotlin-demo.jetbrains.com • http://github.com/Jetbrains/kotlin
  66. 66. Resources CLOJURE • http://www.tryclj.com • http://clojuredocs.org • http://www.clojure-toolbox.com • http://www.clojureatlas.com CEYLON • https://modules.ceylon-lang.org
  67. 67. Resources • http://vschart.com/compare/kotlin/vs/clojure/vs/scala
  68. 68. Book • Seven Languages in Seven Weeks • A Pragmatic Guide to Learning Programming Languages • https://pragprog.com/book/btlang/seven-languages-in-seven-weeks • Ruby • Io • Prolog • Scala • Erlang • Clojure • Haskell
  69. 69. Thank you! José Manuel Ortega Candel | @jmortegac

×