languages on the jvm - groovy, ruby, scala, clojure
DESCRIPTION
A look at 4 languages running on JVM.TRANSCRIPT
1
Dynamic Languages forDynamic Languages forthe Java Virtual Machinethe Java Virtual MachineLee Chuk MunnStaff Engineer
2
Objective
Survey of Dynamic Languages on the
JVM
Groovy, Ruby, Scala, Clojure
3
What to (Not) Expect
> Discussion on a small subset of the languages
> Highlight interesting language features
> How the features are used
> Contrast with Java
> “Hello World”
> Coverage of all features
> Which is a 'better' language
> An apple to apple comparison
> Java bashing
4
Groovygroovy.codehaus.org/
5
Language Features – 1
> Object oriented programming language
> Syntax is very similar to Java – feels very much like Java• Runs Java sources files
> Compact and concise syntax• Optional semi colon and parenthesis • Native syntax for list, maps and regex
> Seamless bi directional integration with Java• Groovy extending Java class implementing Groovy interfaces• Reuse Java's infrastructure – classes, security, threads, etc
> Closure, properties and operator overloading support
> Builder library to easily created nested data
6
Language Features – 2
> Easy integration with Java application with JSR-223• Only one JAR – embeddable/groovy-all-1.x.y.jar
Probably one of the best feature
> MOP provides the meta programming capabilities• A runtime layer which any application can participate
7
A Valid Java Programimport java.util.*;public class Erase {
private List<String> filterLongerThan(List<String> list, int len) {List<String> result = new ArrayList<String>();for (String n: list)
if (n.length() <= len)result.add(n);
return (result);}public static void main(String... args) {
List<String> names = new ArrayList<String>();names.add(“Fred”); names.add(“Barney);names.add(“Wilma”); names.add(“Betty”);System.out.println(names);Erase e = new Erase();List<String> shortNames = e.filterLongerThan(names, 5);System.out.println(shortNames);
}}
8
A Valid Groovy Programimport java.util.*;public class Erase {
private List<String> filterLongerThan(List<String> list, int len) {List<String> result = new ArrayList<String>();for (String n: list)
if (n.length() <= len)result.add(n);
return (result);}public static void main(String... args) {
List<String> names = new ArrayList<String>();names.add(“Fred”); names.add(“Barney);names.add(“Wilma”); names.add(“Betty”);System.out.println(names);Erase e = new Erase();List<String> shortNames = e.filterLongerThan(names, 5);System.out.println(shortNames);
}}
9
Groovy Way
def names = ["Ted", "Fred", "Jed", "Ned"]
println names
def shortNames = names.findAll { it.size() <= 3 }
def diff = names - shortNamesprint diff
Automatically imports java.util.*
Native list syntax
Creates an ArrayList
Closure support
Operator overloading
10
Example – A Groovy Classpublic class Person {
def props = [:]String nameint age = 45def email String toString() { “${name}, ${age}, ${gender}“}Object get(String n) { props[n] } void set(String n, Object v) { props[n] = v }
}
Person fred = new Person(name: 'Fred', email: '[email protected]')
fred.gender = 'male'
Map literal
Statically type
Weakly typeInterpolated string
Dynamic properties
Constructor with property/keyword parameters
Adding a property dynamically
11
Operator Overloading
> Every operator can be overloaded via corresponding method• Eg. plus() for '+', leftShift() for '<<', etc.
> Special method for switch classification• Override isCase()
public class Family {def members = []def plus(Person o) {
members += omembers
}def plus(Family f) {
members += f.membersmembers
}}
Person fred = …Person wilma = …Family flintstones = …flintstone += fred + wilma
Person barney = …Person betty = …Family rubble = …rubble += barney + betty
12
Category
> Add temporary methods or behaviour to instances of object code block
class StringCalcutation {static def plus(String self, String operand) {
try {return (self.toInteger()
+ operand.toInteger()).toString()} catch (NumberFormatException f) {
return (self << operand)}
}}
println “43” + “43” → “4343”use(StringCalculation) {
println “43” + “43” → “86”}
Redefine the behaviour of + in code block
Example adapted from Groovy in Action by Dierk Konig et al
13
Closure
> A piece of code that is wrapped in an object• Anonymous functions/methods• Behaves like data, but can be invoked
def perform(int times, Closure c = {}) {for (i = 0; i < times; i++)
c.call(i)}
perform(3) { x -> println “hello: ${x}” }
Code block not executed but pass to perform a parameter
Parameter
Takes 2 parameters. Closure has a default value
14
Example – New Controls with Closuredef withLock(Lock l, Closure c) {
l.lock();try {
c.call()} finally {
l.unlock();}
}Lock lock = new ReentrantLock()withLock(lock) { println “I have acquired the lock” }
def logInfo = { logger, msg -> logger.log(Level.INFO, msg) }withLock(lock
, logInfo.curry(Logger.getLogger("groovy")).curry("Hello"))
Closure runs in the context of lock
Use curry to wrap parameters
Closure with parameters
15
Meta Object Protocol
> Provides dynamic behaviour to Groovy• Intercept method calls, create classes and methods, etc
> MOP consists of • Formalized behaviour for resolving methods/properties• Two major classes: GroovyObject and MetaClass
> Override methods in MetaClass to redefine behaviour
> Lots of libraries uses MOP
String.metaClass.constructor = {constructor =
String.class.getConstructor(String.class)constructor.newInstance((new Date()).toString())
}
println new String() → “Sat Nov 21 ...”
Replace the default constructor
Example adapted from Groovy in Action by Dierk Konig et al
16
Example – ExpandoMetaClass
def person = new ExpandoMetaClass(Person)person.isAdult = { age >= 18 }
if (person.isAdult())...
> Expando class allows developers to add methods to an object dynamically
> Two variants• Expando• ExpandoMetaClass
17
Example – D&D Style Die RollInteger.metaClass.getProperty = { String sym ->
//Check if sym is in 'd<digit><digit>' formatif (!(sym ==~ /^d\d+$/))
return (-1)def face = sym.split(/d/)[1].toInteger()def rand = new Random()def result = []delegate.times {
result += (Math.abs(rand.nextInt()) % face) + 1 }result
}
//D&D style dice rollprintln 5.d10[2, 7, 5, 4, 10]
18
Rubywww.ruby-lang.org
19
Language Features
> Supports multiple programming paradigm• Imperative, object oriented, functional and reflective
> Excellent support for reflective programming / metaprogramming• Ruby re-introduce to developers
> Continuation support• Freeze a point in the application and return to it later
> Supports reuse through inheritance, mixins and open classes• Open classes is the ability to modify and existing class including
those from the host library Eg. add rot13() method to java.lang.String
20
Example – A Ruby Class
class Song@@plays = 0attr_accessor :name, :artist, :durationdef initialize(n, a, d)
@name = n; @artist = a; @duration = denddef duration_in_minutes
@duration / 60enddef duration_in_minutes=(v)
@duration = v * 40enddef play
@@plays += 1end
end
Methods
Instance member
Class member Create getter/setter for members
Denotes symbol
21
Example – A Ruby Sessions = Song.new("Cavatina", "John Williams", 200)puts s#<Song:0x9dca26>
s.duration_in_minutes = 4s.play
class Song def to_s
"song = #{@name}, artist = #{@artist}, duration = #{@duration}, plays = #{@@plays}"
endend
puts ssong = Cavatina, artist = John Williams, duration = 200, plays = 1
Adding a new method to class
22
Objects and Method Calls
> Everything in Ruby is an object• Objects, things that look like primitive and classes
> Everything is a method call• Message passing masquerading as method calls• obj.fred is the same as obj.send(:fred)as
Method invocation means sending a message to the object Use respond_to? to inspect if an object supports a particular message
• Methods can be invoked with our without parenthesis Excellent for readability and DSL
s = Song.new Invoking the new method on the Song object
x = 1 + 2 – 3 is equivalent to x = 1.+(2.send(:+, 3))
puts ugly_d.quack if ugly_d.respond_to? :quack
23
Reuse
> Inheritance – traditional OO way
> Mixins – adding a module into a class or object• Cannot be instantiated
> Open classes – adding methods to class or object
class KaraokeSong < Song...def to_s
super + “[#{@lyrics}]”end
Inherit from Song
This means super.to_s – from context
module ClassName def class_name
self.class.nameend
end
class Song include ClassName
s = Song.new(...).extend(ClassName)
Mixin to class or instance
class Stringdef rot13
self.tr(“A-Ma-mN-Zn-z”, “N-Zn-zA-Ma-m”)end
end
24
Metaprogramming with Ruby
> Technique by which a program can manipulate another program or itself as their data
> Ruby relies on metaprogramming heavily• Eg. in declaring properties
> Introspect into a a class, object or the running environment• Eg. ObjectSpace.each_object(String) {|x| puts x}
> Allows you to trap interesting behaviour and override or enhance them• Similiar AOP • Eg. creating a method, instantiating an object, invoking a missing
method, querying if a member is defined, etc.• Eg. emulate ExpandoMetaClass in Groovy by trapping
undefined method names
25
Example – Object Instantiation
class Classalias_method :old_new, :newdef new(*args)
result = old_new(*args)result.timestamp = Time.now if defined?(result.timestamp)result
endend
Rename new to old_new
Provide our own 'new'
Set the time if timestamp exists
> Set the creation time on an object if the object has a property call timestamp
Example adapted from “Programming Ruby 2nd Edition by Dave Thomas”
26
Example – Implementing ExpandoMetaClass
> A Groovy feature that allows you to add methods to instances on the fly
class ExpandoMetaClassdef method_missing(name, *args)
if args[0].is_a?(Proc)self.class.instance_eval do
define_method(name.to_s.chomp("="), args[0]) end
elsesuper
endend
end
Add the method to instance
class Foo < ExpandoMetaClassend
f = Foo.newf.greet = lambda {|t| "Hello #{t}!"}f.greet "Fred"
27
Example – JavaBean Style Properties
Caution: This will probably infuriate Rubyists
class Moduledef java_bean(*syms)
syms.each do |s|pn = s.to_s.split("_").collect!{|x| x.capitalize}.joinclass_eval %{
def get#{pn}@#{pn}
end}class_eval %{
def set#{pn}(v)@#{pn} = v
end}
endend
end
class Demojava_bean :my_name…
end
demo = Demo.newdemo.setMyName(“fred”)puts demo.getMyName
28
Continuations
> Abstract representation of “the rest of the code to be executed”
> Uses of continuations• Coroutines, escape and reenter loops/recursion, debugger, need
to backtrack• See
http://repository.readscheme.org/ftp/papers/PLoP2001%5Fdferguson0%5F1.pdf
• Eg. Able to return to the point of exception after handling the exception
if (i > 10)puts “i > 10”
elseputs “i < 10”
end
The continuation of “if (i > 10)” is
puts “i > 10” iff i > 10puts “i < 10” iff i < 10
29
callcc> callcc to construct a continuation object in Ruby
• Encapsulates the state of the application up to callcc• Use the continuation object to jump to just after the callcc
result = 1i = 1callcc {|$cont_obj|}
result *= ii += 1$cont_obj.call if (i <= 10)
Ruby passes a continuation object to block
Returns to the statement just after the callcc
30
Example – Coroutine 1def ping
puts "PING 1"save_and_resume($ping_cont, nil)pongputs "PING 2"save_and_resume($ping_cont, $pong_cont)puts "PING 3"save_and_resume($ping_cont, $pong_cont)
enddef pong
puts "PONG 1"save_and_resume($pong_cont, $ping_cont)puts "PONG 2"save_and_resume($pong_cont, $ping_cont)puts "PONG 3"save_and_resume($pong_cont, $ping_cont)
endping
PING 1PONG 1PING 2PONG 2PING 3PONG 3
Example – Coroutine 1
Example adapted from “Call with Current Continuation Patterns” by Darrell Ferguson, Dwight Deugo
31
Example – Coroutine 2
def save_and_resume(save_cont, resume_cont)callcc {|save_cont|}resume_cont.call unless resume_cont == nil
end
Example – Coroutine
Create a current continuation for the current location
Resume the other routine
32
Scalawww.scala-lang.org
33
Language Features – 1
> A hybrid of object and functional • Supports mutable and immutable (functional) structures• Defaults to immutable for collections
> Objects, singletons and traits• Singleton is a language concept• Very similar to interfaces but with fully implemented methods
Cannot be instantiated, can only be mixed into a class or object
• Supports operator overloading Operators are all method calls
> Statically typed – unique in the world of script language• Can infer types from code during declaration• Easily implement new types and corresponding operations• Supports user define implicit type conversion
34
Language Features – 2
> Statically typed higher order functions• More rigorous in what you can pass to a method
> Supports many high level concurrency abstraction• Monitors, atomic variables, semaphores, workers, channels, etc• Erlang style actors
35
Statically Typed
> Must provide type in method definition• Elsewhere Scala can infer type from the context
var map = Map[Int, String](1 -> “Cat”, 2 -> “Dog”)
• Method return type, variable declaration
> Including closure / functions types• var myfunc: ((Int, Int) => Complex) = null
def sumOfFactors(number: Int): Int = {var sum = 0for (i <- 1 to number)
if ((sum % i) == 0)sum += i
sum}
Method declaration
Inferring from context
36
Defining Classesclass Person(val name: String)
class Employee(override val name: String, val id: String) extends Person(name) {
var salary: Double = _def this(name: String, id: String, _salary: Double) {
this(name, id)salary = _double
}def calculateSalary() = {
salary}override def toString(): String = name + “(“ + id + “)”
}
var fred = new Employee(“James”, “007”)println(fred name) //Equivalent to fred.name
Overloaded constructor
Default constructor
The minimal class
37
Singletons
> Refer to as companion objects in Scala• Use extensively in Scala class libraries
> Defined in the same file as the class• Can access private constructors
object Employee {def apply(name: String, id: String)
= new Employee(name, id)def apply(name: String, id: String, salary: Double)
(verify: (String, String, Double)=> Option[Employee]):Option[Employee] = {
if (verify(name, id, salary))Some(new Employee(name, id, salary))
elseNone
}} Option indicates we may not
be getting any result
Curried function
Singleton
Closure's signature
38
Example – Singletons as Factories
var fred = Employee(“Fred”, “007”)
var obj = Employee("Barney", "001", 12000){(name, id, salary) => (salary <= 10000)}
var barney: Employeeif (obj.isEmpty) {
println("Salary is too high")} else {
barney = obj.get}
Curried function so closure appears outside of formal parameter list
Return type is not Employee
39
Traits
> Similar to interfaces with partial implementations
> Mixed in to classes or selectively to object instances• Mixin to class
• Mixin to instance Only applicable if there are no abstract methods
trait Greetings {def sayHello(name: String) = “Hello “ + name
}
class Employee(override val name: String, val id: String)extends Person(name) with Greetings {
val fred = new Employee(“Fred”, “007”) with Greetings
40
Selective Mixin
> Can enforce mixing traits only into certain classes• By extending from a class
> Late binding with super object
> Resolution of super proceeds from right to left• Eg class A extends B with TraitA with TraitB• TraitB → TraitA → A – assuming B is a class• Excellent for implementing decorator or chain-of-command
pattern
41
Example – Selective Mixin – 1class Person(val name: String) {
def calculateSalary(base: Double) = base}class Employee(...) extends Person(...) {
def mySalary() = calculateSalary(salary)...
}trait SpotBonus extends Person {
override def calculateSalary(base: Double) =super.calculateSalary(base + (base * 0.05))
}
trait ThirteenMonth extends Person {override def calculateSalary(base: Double) =
super.calculateSalary(base + base)}
Can only be mixin to classes that extends from Person
42
Example – Selective Mixin – 2
val fred = new Fred(“Fred”, “007”, 5000) with AnnualLeave with ThirteenMonth
println(“salary = “ + fred.mySalary)
Resolution goes from right to leftFred.mySalaryThirteenMonth.calculateSalaryAnnualLeave.calculateSalaryFred.calculateSalary
43
Operators on Types
> All method calls, no notion of operators• Operator precedence is based on ordering of symbols• Eg. all letters, |, ^, &, < >, = !,:, + -, * % /, all other
special characters• Unary operators, prefixed with unary_
class Employee(override name: String ... //As beforedef +(_salary: Double) = this.salary + _salarydef -:(_salary: Double) = this.salary - _salarydef unary_>>!(): Employee = ... //promotiondef unary_<<!(): Employee = ... //demotion
fred.salary = fred + 1000 //Method call fred.+(1000)fred >>! //promote fred100 -: fred
Instance to follow method
44
Implicit Type Conversion
> Most language have build in implicit conversion• Eg. byte + int – byte is converted to int
> Scala allows you to define implicit conversion• Use implicit keyword on method
var yesterday = 1 days agoclass DateHelper(num: Int) {
def days(when: String) = {var date = Calendar.getInstance()when match {
case “ago” => date.add(Calendar.DAY_OF_MONTH, -num)...
}date.getTime()
}}def implicit convertInt2DateHelper(num: Int) = new DateHelper(num)
Converter that takes 1 and return ???.days(“ago”)
*Adapted from Programming Scala by Venkat Subramaniam
45
Concurrency
> Lots of abstraction to support concurrency and asynchrony• Signals, monitors, atomic objects, semaphores, workers, mail
boxes, actors etc
> Actor based model• A computation entity that inherently concurrent• Communicate by send it messages• Can change its state or spawn new actors based on messages
> Two ways of using actors• actor method• Extend Actor class
> Communications• ! to send a message• receive – case to select message based on type
46
Determining a Prime Number
> Is x a prime number?
> Does x have a factor in the range of 3 to (x – 1)?
> Eg. Is 97 a prime number?• Are there any factors in 3 to 96
> For larger number, partition range and find factors in parallel
> Eg. 97, partition size of 20• 3 – 22 • 32 – 42• 43 – 62• 63 – 82• 83 – 96
97 is a prime number if there are no factors in these ranges
47
Example – Determining Prime – 1 import scala.actors.Actor._val caller = selfdef haveFactorInRange(primSusp: Int, min: Int, max: Int): Boolean = {
((min to max).filter { x => ((primSusp % x) == 0) }.size != 0)}//No checking donedef isPrime(primSusp: Int): Boolean = {
val range = primSusp - 3val partition = (range / 20) + (if ((range % 20) > 0) 1 else 0)var haveFactor = falsefor (i <- 0 until partition) {
val min = 3 + (i * 20)val max = if ((min + 19) > primSusp)
(primSusp - 1) else (min + 19)actor {
caller ! haveFactorInRange(primSusp, min, max)}
}...
}
Creates an actor and starts it
Execute the function and send the result to the collator
48
Example – Determining Prime – 2
def isPrime(basis: Int): Boolean = {...var haveFactor = false
...(0 until partition).foreach { x =>
receive {case ans: Boolean =>
haveFactor = haveFactor || anscase _ =>
}}!haveFactor
}
Waits for messages from actors
Match the message type against the discriminator
49
Clojurehttp://clojure.org
50
Language Features – 1
> Functional language• Verb (functions) is the central theme, not noun (objects)• No side effects – only depends on its arguments
Most Java method do not qualify as functions
• Give the same argument, will return the same result EVERYTIME Functions are stateless f(x) = f(y) if x is equals y
> Higher order functions• f(x) → g(y) where x and y can be functions• Closure is very natural
(defn make-greeter [greetings](fn [name] (format “%s, %s” greetings name)))
((make-greeter “Bonjour”) “Jean”) → “Bonjour, Jean”
Lambda Parameter Body
51
Language Features – 2
> All data is immutable – no data is ever change*
> Everything is a sequence – with abstractions • List – (a b c)• Vector – [a b c]• Set – #{a b c}• Map – {:x a, :y b, :z c}
> Homoiconic – the data structure is the language• No language syntax – just have to know how to write parenthesis • Evaluation rules – first element is the function name
Everything else are parameters
(def foo '(a b c)) → (a b c)(concat foo '(x y z)) → (a b c x y z)foo → (a b c)(def foo (concat foo '(x y z))) → (a b c x y z)
*except for refs
Reassigned
52
Calculate 10!
Javapublic int fac(final int f) {
int tot = 1;for (int i = 1; i < f; i++
tot *= ireturn (tot);
}
Clojure(defn fac [f]
(if (zero? f)1(* f (fac (dec f)))))
Function defintion
53
Calculating 10! with Sequences
> Create a list of values, apply a function over these value• x! = (x – 1)! * x
> Lazy (is a virtue) and infinite sequences• Infinite sequence that will only generate the next value iff required
(appy * (range 1 11))(range 1 11) → (1 2 3 4 5 6 7 8 9 10)(apply * (1 2 3 4 5 6 7 8 9 10))
;generate an infinite factorial list(defn fac
([] 1)([x] (apply * (range 1 (inc x)))))
(defn fac-list [] (map fac (iterate inc 1)))
(take 5 (fac-list)) → (1 2 6 24 120)
Overloaded functionGenerate an infinite sequence of factorials
Take 5 from list
54
Calling Java
> Importing Java classes
> Instantiating Java classes
> Accessing methods and members
> Implementing interfaces and extending classes• With type hints
(import '(java.awt.event ActionEvent)
(def f (new JFrame “Thisis a frame”))(def f (JFrame. “This is a frame”))
(. f setVisible true)(.setVisible f true)(.getDefaultButton (.getRootPane f))
→ (.. f getRootPane getDefaultButton)
(proxy [ActionListener][](actionPerformed [#^ActionEvent a-evt] ( … )))
Type hint
55
Creating a Swing Frame
;Import the relevant classes(import
'(javax.swing JFrame JPanel JButton)'(java.awt.event ActionListener
ActionEvent WindowAdapter WindowEvent))
;Implement an ActionListener(def act-listener
(proxy [ActionListener] [](actionPerformed [a-evt]
(println (format "Button %s pressed" (. a-evt getActionCommand))))
))
Implements the ActionListener
56
Creating a Swing Frame;Create a button(def button
(doto (JButton. "Press me")(.addActionListener act-listener)))
;Create a panel(def panel
(doto (JPanel.)(.add button)))
;Create a frame(doto (JFrame.)
(.setTitle "Hello")(.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)(.add panel)(.addWindowListener win-listener)(.pack)(.setLocationRelativeTo nil)(.setVisible true))
Accessing static member
Apply all following methods to the instance
57
Concurrency
> Supports 4 concurrency model• Agents – actors but pass functions instead of messages• Atoms – like java.util.concurrent.atomic • References – database like transaction semantics
a.k.a Software Transaction Memory (STM)
• Vars – thread local
Agents Atoms References Vars
Shared X X X
Isolated X
Synchronous X X
Asynchronous X
Coordinated X
Autonomous X X
58
Sharing Data via Managed References
Data
bar
fooThread A
Thread B
DataThread A
Thread B
foo
Deference to access immutable data
@foo
Most programming languages eg. Java, C, etc.
59
Agents
> Use to manage independent states
> Send functions to modify the state of the agent• Functions are executed asynchronously• Only one function is evaluated at a time
Some notion of ordering
(def foo (agent 0))@foo → 0(inc @foo) → 1@foo → 0
(send foo inc)
... after some time ...@foo → 1
@foo is immutable
60
References and STM
> References (ref) provide database like transaction
> Software transactional memory• All or nothing• Data are not visible until you commit• All changes happens at constant time
You see all the changes at once
> No inconsistent data during transaction• Thanks to Clojure's immutable data
> All references must occur in a transaction• Create references with ref• Use either alter or commute to change the ref• dosync execute expressions in a transaction
alter and commute will only run in dosync
61
Using ref
(defstruct emp :id :name)(def foo (ref (struct '123 “Fred”))) → {:id 123 :name “Fred”}@foo → {:id 123 :name “Fred”}
(assoc @foo :gender 'male) → {:id 123 :name “Fred” :gender male}
@foo → {:id 123 :name “Fred”}
(dosync(alter foo assoc :gender 'male))
→ {:id 123 :name “Fred” :gender male}(dosync
(commute foo assoc :gender 'male))→ {:id 123 :name “Fred” :gender male}
@foo → {:id 123 :name “Fred” :gender male}
Create a transaction
62
Handling Conflicts with alter
time
(dosync (alter foo assoc ...))
(assoc ...)
> At the start of the transaction• Get an in transaction copy of foo's value (@foo)• Value will never be inconsistent – data is immutable
> At the end of the transaction• foo is check if it has changed during the transaction
f -1(f(x)) = x where x is is the value of foo at the start of the transaction
• If it has, transaction is retried Body of transaction must be side effect free
• Otherwise transaction commits new value visible to all→
(assoc ...)
foo alter by another transaction Transaction is retried
63
Handling Conflicts with commute
> Same semantics as alter• Except at the end of the transaction• If foo is altered, the function is retried
> Function must be commutative and without side effects• Eg. deposit is a commutative operation
You can invoke deposit in any order, the final state of the account will still be consistent
Withdrawal is not commutative
time
(dosync (commute foo assoc ...))
(assoc ...) (assoc ...)
foo alter by another transaction Function is retried
6464
Lee Chuk Munn Staff Engineer [email protected]
65
Relational Algebra (or SQL)
> Nice set of relational algebra to work on sets• Relational algebra is the basis of SQL
>
66
Immutable Data
(def x '(a b c)) a b c
x
(def y (rest x)) a b c
x y
(def z (into x '(d))) a b c
x y
d
z