Slide 1

Slide 1 text

Comparing JVM languages José Manuel Ortega Candel | @jmortegac 26-27 June 2015

Slide 2

Slide 2 text

INDEX Functional programming and closures 2 Language classification 1 Java 8 new features 3 Scala, Groovy, Kotlin, Clojure and Ceylon 4 Advantages and drawbacks 5 Comparing features 6

Slide 3

Slide 3 text

Language classification FUNCTIONAL NOT FUNCTIONAL STATIC DYNAMIC

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Streams

Slide 8

Slide 8 text

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 books=Arrays.asList(book,book1,book2); Predicate 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'}]

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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]

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Java 8 vs Scala

Slide 16

Slide 16 text

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]

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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() 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") } }

Slide 20

Slide 20 text

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 }

Slide 21

Slide 21 text

Kotlin Iterators fun iterateOverCollection(collection: Collection) { for (element in collection) {} } fun iterateOverString() { for (c in "abcd") {} "abcd".iterator() } fun iteratingOverMap(map: Map) { for ((key, value) in map) {} }

Slide 22

Slide 22 text

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) { //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) { //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

Slide 23

Slide 23 text

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) { val book = Book(123456) println(book.title); }

Slide 24

Slide 24 text

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") })

Slide 25

Slide 25 text

Kotlin collections val listInmutable =listOf("Java","Kotlin","Groovy","Scala") val listMutable=ArrayList() listMutable.add("Java") listMutable.add("Kotlin")

Slide 26

Slide 26 text

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++ }

Slide 27

Slide 27 text

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()); }

Slide 28

Slide 28 text

Kotlin vs Java types inference val longJavaClassName = LongJavaClassName() LongJavaClassName longJavaClassName =new LongJavaClassName()

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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" } } }

Slide 33

Slide 33 text

Groovy  Parsing and creating XML

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Java Groovy def languages = ["Java", "Scala", "Clojure", "Kotlin", "Ceylon","Groovy"] def filter = languages.stream() .findAll { it.size()>5 } .collect { it.toUpperCase()} .first() List languages = Arrays.asList("Java", "Scala", "Clojure", "Kotlin","Ceylon","Groovy"); Optional bookFiltered = languages.stream() .filter(s -> s.length()>5 ) .map(s -> s.toUpperCase()) .findFirst();

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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)

Slide 39

Slide 39 text

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])

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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; }}

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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 list = {“string”,true,false} ArrayList list = ArrayList (1, 2, 1.0, 2.0)

Slide 45

Slide 45 text

Comparing featues •Reducing verbosity •Variables definition •Null-safety •Optional types •Lambdas •Pattern matching •Traits •Java interoperability

Slide 46

Slide 46 text

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)

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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 }

Slide 49

Slide 49 text

Optional Types Optional 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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Lambdas in streams List books=Arrays.asList(book,book1,book2); Optional 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)

Slide 53

Slide 53 text

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") } }

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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 { }

Slide 56

Slide 56 text

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):ArrayList { val list = ArrayList() 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)))

Slide 57

Slide 57 text

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 list = new ArrayList<>(); list.add(l1); list.add(l2); }

Slide 58

Slide 58 text

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 })

Slide 59

Slide 59 text

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 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)};

Slide 60

Slide 60 text

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};

Slide 61

Slide 61 text

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) }

Slide 62

Slide 62 text

Comparing features Higher Order Functions Mixin/traits Pattern matching Implicit casts Nullables / Optionals Modules Promote inmutability Lambdas

Slide 63

Slide 63 text

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): List{ 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)} }

Slide 64

Slide 64 text

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.

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Resources CLOJURE • http://www.tryclj.com • http://clojuredocs.org • http://www.clojure-toolbox.com • http://www.clojureatlas.com CEYLON • https://modules.ceylon-lang.org

Slide 67

Slide 67 text

Resources • http://vschart.com/compare/kotlin/vs/clojure/vs/scala

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Thank you! José Manuel Ortega Candel | @jmortegac