asoft
sea

~ tiny vessels of code slowly floating out to sea

Powered by a motorboat called the S.S.Tumblr

1 year ago

@9:13pm

Comments

there are only some ways to mix a cat

A newish mixology

There’s something to be said for the ability of a language allow the design types of objects through composition rather than traditional class inheritance. It awesome! Aristotle was correct in that things can be classified according to thier common characteristics. In software, it seems kind of overly ambitious to think that this should be the same basis of all object design. Most of my own charateristics did not come from my parents. I aquired them from my own schooling and experiences and interactions with others.

Designing software in terms of composition, mixins, also promotes code reuse, because you are isolating areas of concern which can be reused across multiple class hierarchies. When you narrow your areas of concern, your design of modules can be very specialized in a decoupled manner, not really carring about who thier daddy is.

Think about this the next time you add new behavior to your classes: Is your class an instance of iterable or is iterable a trait that can be observed in multiple types? Is your class an instance of a behavior or is the behavior a trait that can be observered in multiple types. It’s all a matter of perspective. Lately I’ve been siding on the former side of the previous two statements.

Mix ins and outs of Scala and Ruby

In Scala, mixin composition comes in the form of abstractions called traits. Languages like Ruby also provide a means of designing software through composition via Modules.

In Ruby you express mixins as:

module A
  def foo
    puts "I was born in module A"
  end
end
module B
  def bar
    puts "I was born in module B"
  end
end
class C
  include A
  include B
end

And in Scala:

trait A {
  def foo = "I was born in trait A"
}
trait B {
  def bar = "I was born in trait B"
}
class C extends A with B

Sometimes it’s you want to mix in behavior for only one instance of a type. In Scala you can do this easly in the form:

class D
val specialD = new D() with A with B

My first attempt do this in Ruby failed. At first I thought I could do something like:

class D
end
special_d = D.new.instance_eval {
  [A, B].each { |mod| self.class.send :include, mod }
} #=> [A, B]

but the D.new.instance_val actually returns an array of A and B so you couldn’t immediately call a module method on the resulting value.

This actually adds the mixin modules A and B to all instances of D defeating the purpose of the exercise.

You can actually create a new anonymous class with the modules A and B included but I couldn’t figure out a way to get this class to extend D inline without D itself being a Module.

mixins_without_d = Class.new { |cls| 
   [A, B].each { |mod| self.class.send :include, mod } 
}

That aside, Scala still has on more trick up it’s sleeve: a static type system.

It’s not really worth bringing up the static verses dynamic type systems debate here, but this static type system does provide a nice built-in mechanism for specifying what kinds of objects the trait can be mixed into.

Way down to mixico

Let’s take and example of cat-like behavior.

In Ruby:

module CatLike
  def pur
    "purrrrrr"
  end
  def meow
    "meeeowww"
  end
end

And in Scala:

trait CatLike {
  def pur = "purrrrrr"
  def meow = "meeeowww"
}

Let’s mix in this behavior.

class Tiger
  include CatLike
end

and in Scala:

class Tiger extends CatLike

The nice thing about mixins that that they can be reused in other objects. Let’s try that.

class Spoon
  include CatLike
end

And in Scala:

class Spoon extends CatLike

Hey! Spoons aren’t very cat-like! Let’s revisit our CatLike mixin.

Me-ouch

In Scala, we can declare what types of objects a trait can be mixed into with self types.

trait Feline
trait CatLike { this: Feline =>
  def pur = "purrrrrr"
  def meow = "meeeowww"
}

Now, let’s try that one more time.

class Tiger extends Feline with CatLike
class Spoon extends CatLike // compile time error!

You can technically do this in Ruby, although you need to cleverly invent your own technique.

module Feline
end
module CatLike
  # how often do you see module authors write code like this?
  def self.included(other)
    raise TypeError.new(
      "%s was not a feline" % other
    ) if !other.included_modules.include?(Feline)
  end
end

class Tiger
  include Feline
  include CatLike
end

class Spoon
  include CatLike # TypeError: Spoon was not a feline
end

Not only is that more verbose, but it requires a metaprogramming trick. The Scala implementation also won’t even compile. A client of the Ruby version of CatLike will not find the error until the class is required.

To be fair I am using static type system idiom for filtering, using a type as inference to what kinds of behavior should be allowed. Let’s revisit that once again with rubyism for determining type: respond_to?.

module CatLike
  # more Ruby-like but still not very common for module authors
  def self.included(other)
    raise TypeError.new(
      "%s did not respond to %s" % [other, "whip_tail"]
    ) if !other.respond_to? :whip_tail
  end
  def pur
    "purrrrrr"
  end
  def meow
    "meeeowww"
  end
end

class Tiger
  def whip_tail
    puts "tail whippin'"
  end
  include CatLike # what? TypeError: Tiger did not respond to whip_tail. Yes it does!
end

The module author thumbs through their copy of the Ruby manual. Whoops! Other in this context is a class object, not the instance of a class.

Let’s revise that module one more time.

module CatLike
  # again, more Ruby-like but even still not very common for module authors
  def self.included(other)
    raise TypeError.new(
      "%s did not respond to %s" % [other, "whip_tail"]
    ) if !other.instance_methods.select { |method| method == :whip_tail }
  end
  def pur
    "purrrrrr"
  end
  def meow
    "meeeowww"
  end
end


class Tiger
  def whip_tail
    puts "tail whippin'"
  end
  include CatLike # ok
end

Can Scala play the duck typing game with cats? Surely it’s going to be way more difficult in a ridged staticaly typed language and we will have to resort to some ugly and verbose reflection code.

Let’s revise our scala mix in using the same type of Rubyism.

trait CatLike { this: { def whipTail } =>
  def pur = "purrrrrr"
  def meow = "meeeowww"
}

class Tiger extends CatLike {
  def whipTail = println("tail whippin'")
}

What’s this? This looks a bit more consise than the Ruby implementation while using a Rubyism and it’s statically typed.

Summary

Though this was a trival example of using mixin composition, I hope we covered some of the strengths of using Scala’s traits as mixins: inline object composition and a built-in means of mixin restriction.

Mixin composition is common in many Ruby libraries, though many module authors don’t restrict the types of objects a module can be mixed into which sometimes leads to meowing flatware.

Most language design exclusions in Ruby can be be worked around using metaprogramming techniques which is really powerfull but sometimes leads to indirectly verbose code, skewing attention away from the original intent of the code.

On most days, if I end out writing less code to do the same task, I consider that a day well spent.

  1. asoftsea posted this
blog comments powered by Disqus