can change shape at runtime. This is a confluence of all the hardest-to-optimize language characteristics. In both cases, the best we can do is to attempt to predict common type and object shapes and insert guards for when we're wrong, but it's not possible to achieve the performance of a system with fully-predictable type and object shapes. Prove me wrong.” - Charles Nutter (JRuby Lead Developer) Tuesday, June 11, 13
in Ruby • Duck typing (i.e., who cares what your actual “type” is) • Malleable language (class definitions modifiable at runtime, methods can be injected virtually anywhere) Tuesday, June 11, 13
to Scala’s traits • Modules can be mixed into classes and other modules • Modules can be mixed into instances after they are created • Encapsulate cross-cutting concerns Tuesday, June 11, 13
that determine which # node is used during failover. module FailoverStrategy include Util # Returns a candidate node as determined by this strategy. # # @param [Hash<Node, NodeSnapshot>] the node snapshots # @return [Node] the candidate node or nil if one couldn't be found def find_candidate(snapshots) raise NotImplementedError end end end No way to define abstract methods Types encoded in comments (tomdoc) Tuesday, June 11, 13
ActiveSupport::Benchmarkable extend ActiveSupport::DescendantsTracker extend ConnectionHandling extend QueryCache::ClassMethods extend Querying extend Translation extend DynamicMatchers extend Explain include Persistence include ReadonlyAttributes include ModelSchema include Inheritance include Scoping include Sanitization include AttributeAssignment # 19 more modules included ... end end Separate concerns are jammed together in a single namespace Tuesday, June 11, 13
"M1 foo"; end end module M2 def foo; puts "M2 foo"; end end module M3 def foo; puts "M3 foo"; end end class A def foo; puts "A foo"; end end class B < A include M1 include M2 def foo; puts "B foo"; end end b = B.new b.extend(M3) b.foo Which foo implementation gets used? No IDE to help you. Must rely on runtime introspection (i.e., method_locator gem) Tuesday, June 11, 13
• Avoid mixin abuse with better abstractions (e.g. type classes, stackable traits, monads) • Encode dependencies and constraints using the type system (e.g., cake pattern) Tuesday, June 11, 13
Ruby/Rails community for providing new behavior for existing classes • Often takes the form of modules that are injected / mixed into existing classes from other libraries • Often invasive and munges together disparate concerns Tuesday, June 11, 13
= KILOBYTE * 1024 GIGABYTE = MEGABYTE * 1024 def kilobytes self * KILOBYTE end alias :kilobyte :kilobytes def megabytes self * MEGABYTE end alias :megabyte :megabytes def gigabytes self * GIGABYTE end alias :gigabyte :gigabytes end Ruby’s Numeric class is re-opened and directly modified. What if other libraries do the same thing at runtime? Collisions. Tuesday, June 11, 13
compile-time enrichment for a particular type • Only one implicit conversion can be used for a given call site • IDE can inform you which implicit conversion is in use for a particular method Tuesday, June 11, 13
# mix everything into Active Record ::ActiveRecord::Base.extend PerPage ::ActiveRecord::Base.extend Pagination ::ActiveRecord::Base.extend BaseMethods # a new Post model now has pagination support class Post < ActiveRecord::Base end Post.page(params[:page]).order('created_at DESC') Tuesday, June 11, 13
for you • Safer alternative to Ruby monkey patching when providing new functionality for existing types • Namespace isn’t further bloated with new methods / behavior since they live in type class instances Tuesday, June 11, 13
a custom module into core collection modules / classes (e.g., Enumerable) or direct modification • Scala’s approach: implicit conversions (safer, less invasive than direct modification) Tuesday, June 11, 13
:except Hash.class_eval do def except(*keys) has_convert = respond_to?(:convert_key) rejected = Set.new(has_convert ? keys.map { |k| convert_key(k) } : keys) reject { |key,| rejected.include?(key) } end end end unless String.method_defined? :underscore String.class_eval do def underscore self.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end end end Brittle approach: Use of class_eval and respond_to? to directly modify classes at runtime Tuesday, June 11, 13
postId: String, userId: String) // build lazy stream of parsed & valid records def parseRecords(path: String): Stream[Record] = { for { line <- Source.fromFile(path).getLines.toStream data <- allCatch.opt { parse[Map[String, String]](line) } text <- data.get("text") if text.trim.nonEmpty postId <- data.get("postId") blogUrl <- data.get("blogUrl") userId <- data.get("userId") } yield Record(blogUrl, postId, userId) } // retrieve first 20 records for a specific user parseRecords(“records.txt”).filter { case Record(_, _, “ryan”) }.take(20) Desugars to filter, map, and flatMap calls. Your own classes can work with for expressions. Tuesday, June 11, 13
use/stack for our service class MyService extends ScalatraServlet with SslRequirement with GZipSupport { // custom service logic goes here (unaware of surrounding functionality!) } Tuesday, June 11, 13
• Ability to easily refactor is extremely important for large code bases • Without constant refactoring, code bases become cluttered with increased technical debt over time as new features are added Tuesday, June 11, 13
are plentiful • Difficult to ensure correctness in a large code base • Tests may pass, but a lurking NoMethodError can await you in production Tuesday, June 11, 13
your back • Immediate feedback when performing invasive refactoring • IDE not necessary, but extremely helpful for revealing types in old or new code Tuesday, June 11, 13
are great for small-scale development • Building complex systems with a highly dynamic language like Ruby is challenging • Scala empowers you to be efficient both at small-scale and large-scale development Tuesday, June 11, 13