神刀安全网

Fowl Types in Ruby

Before we start, my answer would be 100 duck-sized horses.

Duck typing is one of the most important concepts that every Ruby developer should have a strong understanding of.

When I started learning Ruby, I remember struggling with the idea of duck typing. It seemed liked an easy idea to grasp, however, it’s one of those ‘easier said than done’ sort of things.

For example, here is nearly how every other post/video introduces the concept:

If it walks like a duck and quacks like a duck, then chances are….it’s a duck

Reading that you are probably thinking:

"That sounds pretty easy"

But when you get down to it, it’s much more complex than that. However, despite how overused the ‘quacks like a duck..’ saying is, it would still have to be the best way to describe it.

Assumption over Fact

If you have come from any other statically typed language such as Java, you may have noticed that in Ruby, we don’t define our object types.

E.g. we don’t:

Array animals = ['dog', 'cat', 'pig']   

In reality, we really couldn’t give a rats arse about the type of an object in ruby; that is unless it acts like what we think it should .

What I mean by this is that I don’t care what type an object is, as along as it responds to the messages (methods) I send it, I’m happy.

This very feature is one of the reasons that makes Ruby so easy to write and read.

Let’s illustrate this concept with an example.

For this example let’s say you are trying to write a script which will mark an object as ‘dirty’ when one of its attributes has been changed:

def mark_dirty object     object.dirty = true end   

Here, the #mark_dirty method takes any object and sets the dirty? value to true . So how would this work?

Disclaimer: The below code is absolutely horribly written however it’s just used to illustrate the point.

class Dirty     attr_accessor :dirty end  class Dog < Dirty    def initialize breed     @breed = breed   end  end  class Car < Dirty    def initialize model     @model = model   end  end  class Relativity < Dirty   end  def mark_dirty object     object.dirty = true end  # Initialize the Objects dog = Dog.new 'Boxer'   car = Car.new 'Audi'   theory = Relativity.new  # Roll 'em around in the mud mark_dirty car   mark_dirty dog   mark_dirty theory  # Check if they're clean puts car.dirty   # => true  puts dog.dirty   # => true  puts theory.dirty   # => true 

So what’s happening here?

Well, firstly we create a Dirty class. All this class does is give every instance of it an attr_accessor :dirty method.

We then have three separate classes which all inherit from this Dirty class, with two out of the three (which ain’t bad) having their own .initialize method setting various attributes.

We then have the .mark_dirty method from before and call this three times, passing an instance of Dog , Car and Relativity as an argument.

If you check the output, you will notice that all three instances have a dirty value of true thanks to the attr_accessor :dirty in the Dirty class.

We didn’t care whether or not the object that was passed into .mark_dirty had a method of dirty on it, just that it responded to having dirty set to true.

We have created a DirtyObject ducktype i.e. We have objects that walk like they’re dirty and quack like they’re dirty even though they are three unique objects.

To use a real ruby example, objects such as Array and Hash could be argued that they have an enumerable ducktype as they respond to methods such as .each and .inject .

In other statically typed languages, this would not have worked and you would have the .mark_dirty method take only a certain type of object e.g.

def mark_dirty Array list     list.dirty = true end   

To summarize how duck typing works:

Focus on whether or not an object responds to a message opposed to what type of object it actually is.

Discussion

So why is ducktyping useful? Well before in the above example, one clear benefit is that we only need one .mark_dirty method.

In other statically typed languages, we would need a .mark_dirty method for each of the three classes, making the code bloated and not-dry.

A problem with ducktypes though is that it can lead to exceptions being raised. This may lead to some using the kind_of? method to check the class of an object that is passed into the method.

However, this really isn’t the best approach. Why?

Well because if you think of above example, we would have to check that the object passed in was either a Dog , Car or Relativity . Three isn’t so bad, but what happens if you have 100 objects that respond to that method? You might then say to just call object.class.superclass to see whether or not it inherits from Dirty . However what happens when you have a class that inherits from Dog ? Calling object.class.superclass will return Dog and therefore kind_of?(Dirty) will return false, even though the object does respond to .dirty .

Another argument might be to call respond_to? on the object which is right in checking whether or not the object responds to the method however it can fail if method_missing has been altered.

Dave Thomas, Author of the famous PickAxe answered this one perfectly so I’ll leave it to him.

“You don’t need to check the type of the arguments. If they support << (in the case of result) or title and artist (in the case of song), everything will just work. If they don’t, your method will throw an exception anyway (just as it would have done if you’d checked the types). But without the check, your method is suddenly a lot more flexible: you could pass it an array, a string, a file, or any other object that appends using <<, and it would just work.”

I hope this post helped explained this concept to you – if you have any questions or suggestions please let me know in the comments below!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Fowl Types in Ruby

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮