views:

1032

answers:

4

Hello, I am new to Ruby and currently trying a few examples from the Ruby book I am using as a guide:

    class Account
attr_accessor :balance
def initialize(balance)
  @balance = balance
end  
end

class Transaction
def initialize(account_a, account_b)
@account_a = account_a
@account_b = account_b  
end  

def debit(account,amount)
  account.balance -= amount
end
    def credit(account,amount)
        account.balance += amount
    end

  def transfer(amount)
      debit(@account_a, amount)
      credit(@account_b, amount)    
  end

end

savings = Account.new(100)
checking = Account.new(200)
trans = Transaction.new(checking, savings)
trans.transfer(60)

puts savings.balance
puts checking.balance

This is a pretty straightforward example containing two classes within the same script file. I am confused about the type of the argument I am passing to the credit and debit methods. Coming from Java, I am still thinging in terms of types, so obviously the type of the account variable i am passing to,say the debit method, has to be of type Account.

Since ruby is dynamically typed and is not checking for type, how can I safely operate on the argument I am passing, and define the rest of the method by saying: account.balance -+ amount ?

I am trying to understand, what kind of safety is there if I pass to the debit method a reference to an object other than Account?

When the body of the method below is defined, it uses the given parameter account. Now, I guess I am repeating myself because I still can't grasp the idea... How can I take the argument account (which can be of any type, since no-one is checking) and build some logic by using the dot operator, ask for its instance variable, or call its other methods, and perform calculations on an object which might, or might-not be, the correct kind (or type)? Of course, implicitely, I want it to be of type Account.

def credit(account,amount)
        account.balance += amount
    end

Also, how would the same example work if I declared the two classes in different files?

Sincere apologies in advance for the newbie questions, I just find it hard to wrap my mind around the dynamic typing -- or better, no type checking. The book is either a bit vague on this, or I can't shake my tunnel-vision by thinking only in java.

Any practical explanations would be greatly appreciated.

+2  A: 

It's called "duck typing" - I suggest reading the linked Wikipedia article first, and then see if you still have any questions remaining after that.

Pavel Minaev
So basically, I can operate as however I want on an argument I am passing to that method, assuming it will work fine, as long as the caller of that method passes a reference to an appropriate object.. or an object that can handle what the method is asking it to do?
denchr
Thanks for the link. Will read that article.
denchr
+1  A: 

Actually, all of the modern languages are type-safe and memory-safe, just not always statically typed


Those are good questions, and it's really the core of the dynamic-vs-static typing paradigm.

As it happens, most of your worries aren't too serious:

Ruby is type-safe, at least by its own definition, and it's definitely memory-safe. It does check types on every operation. (Sort of, some operations like parameter passing have no type restrictions to check.)

It does have a lot of automatic conversions, but it's still type-safe.

It does have dynamic typing, but it's still type-safe.

It's true, there is no "wrong" type for a method parameter. The check will happen when you use that parameter. There is also no need to check to see what type you received ... Ruby will check for you when you use it. You just get your errors at runtime instead of compile time, but you were going to unit test anyway, right? :-) It is useful to look up "Duck typing", as the definition of compatible types is wider than usual in Ruby, but there is still a definition and it is checked.

For a very large program, conventional wisdom maintains that statically typed languages like Java and Scala are safer choices, because they can be refactored with confidence and you get the extra compile-time checks. The conventional wisdom is disputed by many people, however, as it involves reducing a complex set of trade-offs to a 1 bit conclusion.

DigitalRoss
You're right. Thanks for pointing out that detail.
denchr
What? What makes you think Ruby checks types? As long as the object passed responds to the messages you send it, Ruby doesn't care at all what type it is.
Chuck
I guess what perplexes me most, now that I am trying to transition to ruby, is calling methods on a parameter via the dot notation, when I have no guarrantee that the client of the method will indeed pass a "correct" parameter..
denchr
Chuck: I was wondering what sort of type errors the questioner was thinking of. A C-style *segmentation fault* error? I mean, if an object responds to the send it has had its type checked and it passed, otherwise, the type check leads to an exception. I think we have all got so used to the strong typing in modern languages that we take it for granted. There was a day...
DigitalRoss
That's a very broad statement in reply. What about Tcl? Perl?
Pavel Minaev
I had doubts myself about phrasing it that way, tho last week I used "type-safe" and someone complained about that, despite the fact that "type-safe" as used with real languages doesn't even have a precise definition. It looks like the term I really want is "memory safe", meaning arbitrary bit patterns can never be used. But Ruby does appear to be easily classified "type-safe". (Only the formal definition is precise, but not even Java meets that useless-in-practice standard.)
DigitalRoss
Ok, I've revised it to use more specific terms. I now claim that Ruby is definitely *memory safe* and that it is *type safe* by its own definition of type compatibility.
DigitalRoss
+3  A: 

You don't have any type safety in Ruby. All that matters to Ruby is whether an object can respond to the messages it's receiving. You can pass in something completely nonsensical and Ruby won't do anything to stop you at that point. But if you pass in an object that doesn't respond to the messages you're sending (+ and - in this case), you'll get a NoMethodError when your code tries to send the invalid message.

In general, the solution to this is: Don't pass in the wrong type. It know it sounds weak, but that's pretty much what you need to do — ensure you're passing the right thing. Write tests for your programs to make sure you're doing what you mean to do. Ruby is very big on unit-testing. If you're really worried about an argument being the correct kind of thing, you can either explicitly check its class (raise 'WTF?' unless object.class == String) or you can try to convert it to the right class (by defining a to_foo-type method).

In return for this, you're largely freed from caring about types. As long as the object responds to the messages you send, it doesn't matter what you pass in. This makes things like mocks and proxies really easy.

Chuck
So, it might help me if I think of it like this:During method definition - ask the argument to do various stuff in the body of the method as if it'll work just fine.When a client calls that method - It can pass any parameter (looks like a duck), as long as it responds to whatever it is in the method body defined above (might quack like a duck)
denchr
Chuck: I'm sure this is what you meant, but just in case: sure, you don't have any *compile time* type safety in Ruby. But like all modern languages, it's very strongly typed in the end. It's not picky, the only check is "can the method be called", but there is no way around that check, except to catch any resulting exception and quickly define the method. :-)
DigitalRoss
The statement "you don't have any type safety in Ruby" is technically incorrect. Actually, all of the modern languages are strongly typed and type safe. It's easy to forget what an actually not-type-safe language is like: C, Fortran, and Assembly are three examples.
DigitalRoss
@DigitalRoss: There's not even a type-based check to see if the method can be called. If an object doesn't have a method to handle a particular message, it just falls through to the default method, `method_missing`. You can respond to totally arbitrary methods at runtime without catching any exceptions. As for type safety: The term (as most people I know use it) means that one type cannot be found where another was expected. Is this the case in Ruby? Absolutely not. Aside from an explicit test, there isn't even a way to say that you expect a particular class.
Chuck
+1  A: 

The object passed, its reference, can be considered unknown at first glance, but Ruby is strongly typed; in addition, it has features that use "duck typing" to its benefit. I'll show a few examples.

Lets use the example of financial accounts.

You can make use of a case statement to determine the type of an object:

class Account
  attr :balance # add ", true" to make it writeable
  def initialize(balance)
    case balance
      when String: @balance = balance.to_f
      when Fixnum, Float: @balance = balance
    else
      raise TypeError, "Can't initialize an account with a balance of type #{obj.class}."
    end
  end
end

Another option, when you've one test to make is the is_a? test of the object, like obj.is_a?(Fixnum) will return true/false on whether or not it is an Integer.

You can use an assertion to enforce the type:

class Account
  attr :balance # add ", true" to make it writeable
  def initialize(balance)
    assert_eql balance.class, Float
    @balance = balance
  end
end

I would suggest that for the Transaction class you use an if-statement in conjunction with raising an exception:

class Transaction
  def initialize(account_a, account_b)
    raise ParameterError, "Both parameters must be accounts!" unless account_a.is_a?(Account) && account_b.is_a?(Account)
    @account_a = account_a
    @account_b = account_b  
  end

  def transfer(amount)
    debit(@account_a, amount)
    credit(@account_b, amount)    
  end

  private

  def debit(account,amount)
    account.balance -= amount
  end

  def credit(account,amount)
    account.balance += amount
  end
end
The Wicked Flea