A Method to the Madness

DRAFT WIP - Raw Writing

Now that we've assembled the team it's time to learn how to make them work together and find a method to the madness.

Def Cool

So how do we make a method? Turns out it looks a bit like a block:

def my_add(a, b)
  a + b
end

my_add(1, 2) # => 3

We have arguments (a and b) and an action a + b, so that block would have looked something like

{ |a, b| a + b }

Except this way is def cooler.

Defaults

Sometimes you don't want to specify everything, you just want some sane defaults to lay back on and take care of things behind the scenes!

Well Ruby can do that:

def add(a, b = 1)
  a + b
end

add(2) #=> 3

All we have to do is put an equals sign right after it and tell it what it should be if we go and forget to tell it what it is.

One Arg, Two Arg, Three Arg....

Say you want to take multiple arguments at once, that'd be called a varadic argument, or one that'll grab up whatever you're kind enough to leave in its way:

def joins(*words)
  words.join(' - ')
end

joins('some', 'words', 'and', 'a', 'few', 'more')

If you use a varadic argument, you can still use other params as well before it and the *name will just grab the rest afterwards.

What's the Keyword?

Maybe having something like add(1,2) makes sense, but what if we get something a bit more complicated and we forget which order we put the args in or what they even were? Keyword arguments let us call a method with names to clear a few things up:

def joins_kw(words:, delimiter: ' - ')
  words.join(delimiter)
end

joins_kw(words: %w[some words])

Keyword arguments without a default to the right are required, the others behave just like default arguments with regular args.

Now you could always use the old way too and have it capture anything that looks like a hash, kinda like varadic args above:

def dictionary_print(**words)
  words.each { |word, definition| puts "#{word}: #{definition}" }
end

dictionary_print(
  lemurs: 'awesome',
  bacon:  'chunky'
)

Mmmm, chunky bacon.

Note that like varadic arguments you can also use keyword arguments with this for required or optional params and let **name slurp up the rest.

Return of the King

Now how exactly are we getting things out of those methods? You probably noticed that we're just magically getting the values out the other side, right?

Turns out whatever happened last in a method or block is what it's going to return. Let's take a look at add again:

def add(a, b)
  a + b
end

Since a + b is the last thing before the method ends, that's what we're going to get back. The right side of an = sign is also a thing that returns a value.

It's called an expression, and lots of things in Ruby are expressions. Remember if, case, and ?: (ternary) ? They are too!

value = condition ? value_if_true : value_if_false

other_value = if other_condition
  1
else
  2
end

one_more_value = case 'string'
  when 'string' then 1
  else 2
end

That also means you can stick them at the end of a method and get a value back too!

Question is though, how do we return when we're not at the end? We use return!

def dictionary_print(**words)
  return false if words.empty?

  words.each { |word, definition| puts "#{word}: #{definition}" }
end

That's called a guard statement, we're guarding the rest of our method from silly things we want to catch early. That said, each will just do nothing with an empty Hash or Array.

Now we don't have to return at the top, it could be anywhere in there, who knows. The silly thing to do though is to use return at the bottom because Ruby already knows it's returning that.

Blockhead!

( I debate if this should be in an advanced section considering the detail on yield, block_given?, and passing it in as a param &block, but it's also needed to explain what the next section does so.... )

You've seen a few methods that take blocks, like each. What if we want our own? Turns out there are a few ways, and one will definitely feel more magical than the other.

We yield:

def block_method
  yield 'a'
end

block_method { |v| puts v }

What that does is it throws 'a' to our block, which gets named v as a variable, then we print it out to the screen.

Problem is, if we call that method without a block now it'll crash:

2.3.3 :013 > block_method
LocalJumpError: no block given (yield)
    from (irb):11:in `block_method'
    from (irb):13
    from /Users/lemur/.rubies/ruby-2.3.3/bin/irb:11:in `<main>'

We could always check if there's a block first:

def block_method
  yield 'a' if block_given?
end

2.3.3 :015 > block_method { |v| puts v }
a
 => nil
2.3.3 :016 > block_method
 => nil

Really though this feels like a lot of magic and can be a bit confusing, so how about we look at a more explicit way to do things.

We can also explicitly pass a block in:

def block_method(&block)
  block.call('a') if block
end

2.3.3 :018 > block_method
 => nil
2.3.3 :019 > block_method { |v| puts v }
a
 => nil

It works much the same way, except now we have a very explicit name for what we're doing here instead of some magic around yield and block_given?.

An Order to Everything

( Cover ordering of params, how you can mix and match )

Now that we have all these things, we have to remember that Ruby likes things to be nice and in order:

def example(a, b, *var_args, c:, d:, **kw_args, &block)
  puts "a:        #{a}"
  puts "b:        #{b}"
  puts "c:        #{c}"
  puts "d:        #{d}"
  puts "var_args: #{var_args}"
  puts "kw_args:  #{kw_args}"

  block.call(a,b,c)
end


2.3.3 :020 > example(1, 2, 3, 4, a:1, b:2, c:3, d:4) { |a, b, c| a + b + c }
a:        1
b:        2
c:        3
d:        4
var_args: [3, 4]
kw_args:  {:a=>1, :b=>2}
=> 6

Mix those up and Ruby will throw a fit:

  1. Regular Arguments
  2. Varadic Arguments
  3. Keyword Arguments
  4. Varadic Keyword Arguments
  5. Block Argument

Of course this is a bit of an extreme example.

results matching ""

    No results matching ""