Static or Dynamic Typing?
Why Not Both?
Mixing JRuby and Scala
Mario Camou
@thedoc
Slide 2
Slide 2 text
Agenda
• Why?
• Why Scala?
• Calling Scala from JRuby
• Calling JRuby from Scala
• Q&A
Slide 3
Slide 3 text
Why?
Slide 4
Slide 4 text
Why?
Ruby (and dynamic languages in general) are
great
• Rapid development
• Flexibility
• Duck-typing
• Metaprogramming
Slide 5
Slide 5 text
Why?
...but there are some pitfalls
• Integration
• Correctness
• Performance
• Productivity
Slide 6
Slide 6 text
Integration
JRuby gives you access to any Java libraries
and frameworks...
Slide 7
Slide 7 text
Integration
...but not all of them are JRuby-friendly
(even though JRuby keeps getting better!)
• Class names and actual classes (beyond jrubyc)
• Method signatures
• Overloaded methods
• Type erasure in generics
• Subclassing
• Annotations
• Executable JAR files
• Legacy applications
Slide 8
Slide 8 text
Integration
Use statically-typed proxies to bridge the gap
http://www.geekologie.com/2012/03/endless-possibilities-universal-construc.php
Slide 9
Slide 9 text
Correctness
• Static analysis
• Refactoring
• Self-documentation
• Type errors
• Unit tests can help...
• ...but they have to be complete...
• ...and they don’t cover all possible scenarios...
• ...and tracking down type errors can be Hell
http://evanfarrer.blogspot.com.es/2012/06/unit-testing-isnt-enough-you-need.html
Slide 10
Slide 10 text
Correctness
• Use a statically-typed language for critical or
library code
• Use a dynamically-typed language for high-
level dynamic code and DSLs
http://olabini.com/blog/2008/06/fractal-
programming/
Slide 11
Slide 11 text
Performance
JRuby performance is great and getting
better...
(and it doesn’t matter if the application is waiting for the
user 10 times faster)
Slide 12
Slide 12 text
Performance
• For some tasks, static typing is faster
• Heavy computation
• Method lookup
• method_missing
• Some benchmarks:
http://shootout.alioth.debian.org/u32/performance.php
Slide 13
Slide 13 text
Performance
Implement performance-critical tasks in a
compiled statically-typed language
Slide 14
Slide 14 text
Productivity
• Refactoring (again!)
• Code navigation
• IDE help (method parameters, autocomplete, ...)
Slide 15
Slide 15 text
Agenda
• Why?
• Why Scala?
• Calling Scala from JRuby
• Calling JRuby from Scala
• Q&A
Simplified Syntax
• Case classes
case class Person (firstName:String, lastName:String)
• Type inference
val m = new HashMap[Int, String]
• No getters / setters
Unless you really need them
• More flexible method names (think DSLs)
Use (almost) any character
Translated to legal names in bytecode (i.e., + is $plus, += is $plus$eq)
Slide 18
Slide 18 text
Functional Programming
Mixed OO - Functional model
• Closures / partial functions
• foreach, map, fold, filter, ...
• Define your own control structures
• Immutable eager and lazy values
• Pattern matching
• For comprehensions
Slide 19
Slide 19 text
Traits
• Interfaces with method definitions
• Can be used for mix-ins
• Calling the previous method in the chain
with no aliasing
• Dependency injection (“cake pattern”)
Slide 20
Slide 20 text
Structural Types
• Declare what you need, not the type
• Statically-typed duck typing
class Foo { def x = "Foo.x" }
class Bar { def x = "Bar.x" }
def doIt (arg: { def x:String }) = arg.x
scala> doIt(new Foo)
res0: String = Foo.x
scala> doIt(new Bar)
res1: String = Bar.x
Slide 21
Slide 21 text
Implicits
• Automatically convert one object to another type
• Solve some of the same problems as open classes
class MyRichString(str: String) {
def acronym = str.toCharArray.foldLeft("") { (t, c) =>
t + (if (c.isUpperCase) c.toString else "")
}
}
implicit def str2MRString(str: String) = new MyRichString(str)
scala> "The HitchHiker's Guide To The Galaxy".acronym
res0: java.lang.String = THHGTTG
Slide 22
Slide 22 text
Implicits
http://www.codecommit.com/blog/ruby/implicit-conversions-more-powerful-than-dynamic-typing
In Ruby:
class Fixnum
alias_method :__old_lt, '<'.to_sym
def <(target)
if target.kind_of? String
__old_lt__ target.size
else
__old_lt__ target
end
end
end
In Scala:
implicit def newLT(i: Int) = new {
def <(str: String) = i < str.length
}
scala> 1 < "foo"
res0: Boolean = false
scala> 5 < "foo"
res1: Boolean = true
Slide 23
Slide 23 text
The Dynamic Trait
• Similar to method_missing
• Experimental in 2.9, available in 2.10
object Test extends Dynamic {
def applyDynamic (method:String) (args: Any*) {
println ("%s (%s)".format(method, args.mkString(",")))
}
}
scala> Test.foo("bar",'baz, 1)
foo (bar,'baz, 1)
Akka
• Based on the Actor model (Erlang)
• Message passing
• Transparent distribution
• Messaging system integration
• AMQP
• Apache Camel
• HTTP
• ...
• Software Transactional Memory
Slide 26
Slide 26 text
Akka
• Mikka: Actors in JRuby by Theo Hultberg
(@iconara)
• Thin wrapper around Akka Java API to
make it more Ruby-like
• https://github.com/iconara/mikka
• ...for more info ask Theo!
Slide 27
Slide 27 text
Agenda
• Why?
• Why Scala?
• Calling Scala from JRuby
• Calling JRuby from Scala
• Q&A
Slide 28
Slide 28 text
Calling Scala from JRuby
• Just like Java!
• JRuby sugar
• 1.6.0+
• 1.6.6+
Slide 29
Slide 29 text
Just like Java!
In Scala:
package app.helpers
import scala.reflect.BeanProperty
class Foo {
private var question: String = ""
@BeanProperty var a: String = ""
def questionAndAnswer = "Unknowable"
def setQ(s:String) = { question = s }
def getQ = question
}
In JRuby:
require ‘java’
=> true
f = Java::app.helpers.Foo.new
=> #
f.q = "Life, the Universe and Everything"
=> "Life, the Universe and Everything"
f.a = "42"
=> "42"
f.q
=> "Life, the Universe and Everything"
f.a
=> "42"
f.question_and_answer
=> "Unknowable"
https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
Slide 30
Slide 30 text
Just like Java!
Closures
In Scala:
package app.helpers
class Foo {
def test(x:Int, y:Int, z:Int,
f:(Int, Int) => Int) = {
f(x,y) == z
}
}
In JRuby:
f = Java::app.helpers.Foo.new
=> #
f(1, 2, 3) { |x, y| x + y }
=> true
Slide 31
Slide 31 text
JRuby Sugar - 1.6.0+
Singleton (Scala object) support
Call Singleton methods just like static/class methods
In Scala:
package app.helpers
object Foo {
def test = "Static method"
}
In JRuby:
require ‘java’
# => true
Java::app.helpers.Foo.test
# => “Static method”
Slide 32
Slide 32 text
JRuby Sugar - 1.6.6+
Operator aliases
$plus -> +
$minus -> -
$div -> /
$plus$eq -> +=
apply (a.k.a. ()) -> []
update (a.k.a. ()=) -> []=
...
There are some caveats... see
https://github.com/jruby/jruby/wiki/Integrating-with-Scala
Slide 33
Slide 33 text
Agenda
• Why?
• Why Scala?
• Calling Scala from JRuby
• Calling JRuby from Scala
• Q&A
Slide 34
Slide 34 text
Calling JRuby from Scala
• JRuby Embed API (RedBridge / JSR-223)
• Scuby
Slide 35
Slide 35 text
JRuby Embed API
val container = new ScriptingContainer
val receiver = container.runScriptlet("""
# Radioactive decay
def amount_after_years(q0, t)
q0 * Math.exp(1.0 / $half_life * Math.log(1.0/2.0) * t)
end
def years_to_amount(q0, q)
$half_life * (Math.log(q) - Math.log(q0)) / Math.log(1.0/2.0)
end
""")
container.put("$half_life", 24100) // Plutonium
val args = Array[Object](10.0:java.lang.Double, 1000:java.lang.Integer)
val result = container.callMethod("amount_after_years", args, Double.class)
https://github.com/jruby/jruby/wiki/RedBridgeExamples
Scuby Goals
• Thin DSL layer between Scala and JRuby
• Simplify calling JRuby
• Calling JRuby should be as transparent as possible
• Static typing as far as possible
Slide 38
Slide 38 text
Usage
To use Scuby:
• Download the artifacts from Maven Central
• https://oss.sonatype.org/content/repositories/releases/cc/
abstra/pasilla/scuby/0.1.8/
• Add scuby-0.1.8.jar to your CLASSPATH
• Use Maven (or SBT, Gradle, ...)
• groupId: cc.abstra.pasilla
• artifactId: scuby
• Current version: 0.1.8 Add Scuby to your project file
Slide 39
Slide 39 text
Assumptions & Defaults
• Single JRuby engine
• For our needs, we don’t need more
• You don’t have to pass in the engine to every call
• Singleton interpreter scope (default)
• Otherwise you can get things like nil != nil
• Can be changed before first JRuby call
• Transient local variable behavior (default)
• Local variables don’t survive multiple evaluations
• If you need them to persist, store in a Scala val (and pass as
parameter)...
• ...or change before first JRuby call
Example Ruby File
# File test.rb (from the Scuby tests)
module Core
class Person
attr_accessor :firstname, :lastname
def initialize (firstname, lastname)
@firstname = firstname
@lastname = lastname
end
def fullname
"#{firstname} #{lastname}"
end
def get_label
javax.swing.JLabel.new(fullname)
end
end
...
Slide 42
Slide 42 text
Example Ruby File
...
module Backend
def self.get_people
# Get data from the backend and return an Array of Person
end
def self.get_data
{ :people => get_people, :other_data => get_other_data }
end
def self.get_person(name)
# Get a person's data from the DB and return a Person object
end
def self.get_other_data
# Get some other data that is needed for the app
end
end
end
Slide 43
Slide 43 text
require & eval
import cc.abstra.scuby.JRuby._
// Require a Ruby file from the classpath
require("test")
// Eval a Ruby statement discarding the return value
eval("import Core")
// Eval a Ruby statement that returns a Ruby object
val array = eval[RubyObj]("[]")
// Or with type inference
val array2:RubyObj = eval("[]")
Slide 44
Slide 44 text
Creating Objects
import cc.abstra.scuby._
// Create a Ruby object
val array3 = new RubyObject('Array)
// Create a proxy object for the Ruby BackEnd class
val backend = RubyClass('Backend)
// Create an instance of the Person class
val person = new RubyObject('Person, "Zaphod", "Beeblebrox")
val person2 = RubyClass('Person) ! ('new, "Ford", "Prefect")
Slide 45
Slide 45 text
Calling Methods
// Call a method on a Ruby object (in this case, the Ruby class),
// passing in parameters, and get back another Ruby object
val zaphod = backend ! ('get_person, "Zaphod")
// Call a Ruby method with no parameters
val data = backend ! 'get_data
// Ruby method chaining
val length = backend ! 'get_people ! 'length
// Get a reference to a Ruby method that can later be called
val getPerson = backend --> 'get_person
// Call the method. Returns an AnyRef.
// With the above, these 2 lines are equivalent:
getPerson("Zaphod")
backend('get_person, "Zaphod")
// Call a Ruby method which returns a Java object,
// in a type-safe way
val label = person.send[JLabel]('get_label)
Slide 46
Slide 46 text
Arrays and Hashes
// Access to a Ruby Hash or Array (i.e., anything that implements [])
// and creating a Ruby Symbol using %
val people = data(%('people))
val zaphod2 = people(0)
// Multidimensional Hashes or Arrays (i.e., data["parm1"]["parm2"])
val ford = data(%('people), 1)
// Modify/add an element to a Hash or Array (or anything that
// implements []=)
people(2) = RubyClass('Person) ! ('new, "Arthur", "Dent")