Ruby functions

From wikinotes
Revision as of 00:45, 15 September 2020 by Will (talk | contribs) (→‎procs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

invocation

Parentheses are optional, but very helpful.

get_greetings "alex"
get_greetings "alex", "will"
get_greetings("alex", "will")

return

In ruby, the last statement is always the return value.

def printhi
  puts "hi"
end

You can also use return to break out early from a function.

def get_greeting(name)
  if name == 'will'
    return "hello me, it's me again"
  "hello " + name
end

function suffix conventions

Ruby has a convention of using suffixes in function names to indicate specific behaviours.
These suffixes have no syntactic significance, they are simply regular characters and a part of the function name.

  • method? returns a bool
  • method! permanent, or dangerous operation.
    normally means modifying the instance, instead of returning new instance.
    • Enumerable#sort returns a sorted version of the object while Enumerable#sort! sorts it in place
    • In Rails, ActiveRecord::Base#save returns false if saving failed, while ActiveRecord::Base#save! raises an exception.
    • Kernel::exit causes a script to exit, while Kernel::exit! does so immediately, bypassing any exit handlers.

wrapping functions

Ruby supports 4x types of arguments and an optional do block.
This is how you can wrap all of them.

def foo(a, b=1, *c, d:, **e)
  puts a, b, c, d, e
  yield if block_given?
end

def wrapper(*args, **kwargs, &block)
  foo(*args, **kwargs, &block)
end

procs

Procs behave similarly to lambdas. They are code-blocks that are executed when their method call is called.

# anynymous function
proc = Proc.new { puts("hello") }
proc.call

proc = Proc.new { |x, y| puts(x, y) }
proc.call(1, 2)
# &:method refers to a method you want to call on an object
# 
# for example, this calls method 'upcase' on each input object ex:  {x}.upcase 
["alex", "courtney", "sam"].map(&:upcase)

If filtering, you can do the opposite

[1, 2, 3, 4].filter(&:even?)         # even numbers
[1, 2, 3, 4].filter {|x| !x.even? }  # odd numbers

lambdas

foo = lambda { puts "bar" }
foo = -> { puts "bar" }
foo.call()

foo = lambda { |x| puts x }
foo = ->(x) { puts x }
foo.call("bar")

Lambda can also work as partials, providing defaults to keyword arguments.

foo = lambda { |x="abc"| puts x }

Currying

def foo(a, b, c)
  puts a, b, c
end

fn = lambda { |b, c| foo("a", b, c) }
fn.call("b", "c")

blocks

&block

Using &block is the same as yield, you just declare a name for the callback you are invoking.

def blocker(&block)
  block.call
  puts "you"
end

blocker { puts "hello" }
#> hello
#> you

blocker do
  puts "hello"
end
#> hello
#> you

yield

Yield without args

def yielder
  yield
  puts "you"
end

yielder { puts "hello" }
#> hello
#> you


yielder do
  puts "hello"
end
#> hello
#> you

Yield with args

def yielder
  yield("you")
end

yielder { |x| puts "hello, #{x}" }
#> hello, you

yielder do |x|
  puts "hello, #{x}"
end
#> hello, you

Check if a yield block was provided using block_given?.

def yielder
  yield if block_given?
end

params

positional params

def print_greeting(name)
  puts "hello #{name}"
end

keyword params

  • c is a required keyword argument (no default)
  • d is a keyword argument with a default
def foo(a, b, c:, d: nil)
  puts bar
end

foo(1, 2, c: 3, d: 4)
foo 1, 2, c: 3, d: 4

hash params

  • before keyword params were introduced, you could use hash params
def foo(options = {})
  bar = options.fetch(:bar, 'default')
  puts bar
end

foo(bar: 'baz') # => 'baz'

variable num of params (splat)

def print_greetings(*names)
  names.each { |name| puts "hello #{name}" }
end

unpack hash as keyword arguments (double-splat)

def foo(**bar)
  puts bar
end
foo(a: 1, b: 2, c: 3)
#> {:a=>1, :b=>2, :c=>3}


argument type order

def testing(
  pos, 
  eq = 1, 
  *args, 
  kwd: 1, 
  **kwargs
)
  puts "hi"
end

super wrapper

* used alone accepts and ignores all arguments.
Note that super will receive all of the original arguments automatically.

class Foo
  def multiprint(a, b)
    puts a, b
  end
end

class Bar < Foo
  def multiprint(*)
    super
  end
end

Bar.new.multiprint(1, 2)
# 1
# 2