Feeling Objects: Pattern Matching in Ruby

Post on 05-Dec-2014

267 views 2 download

description

 

Transcript of Feeling Objects: Pattern Matching in Ruby

Friday, April 5, 13

Feel ObjectsPattern Matching in Ruby

Friday, April 5, 13

Name: Ryan Levick

Friday, April 5, 13

Work: 6Wunderkinder

Friday, April 5, 13

Twitter : @itchyankles

Friday, April 5, 13

Friday, April 5, 13

What the hell is pattern matching and why should I

care?

Friday, April 5, 13

Wikipedia Definition

“...the act of checking a perceived sequence of tokens for the presence of the constituents of some pattern.”

Friday, April 5, 13

Wat?

Friday, April 5, 13

My Best Try

Checking a data type against some predefined patterns, and when that data type matches one of the predefined patterns, do something.

Friday, April 5, 13

In Short...

Pattern matching is another way to do control flow.

Friday, April 5, 13

Death to “if-then-else”!

Friday, April 5, 13

Friday, April 5, 13

ein Beispiel

Friday, April 5, 13

fun sum(numbers) = case numbers of

[] => 0x::xs => x + sum(xs)

Friday, April 5, 13

I don’t know SML. What the hell is this?

Friday, April 5, 13

Function declaration

fun sum(numbers) = case numbers of

[] => 0 x::xs => x + sum(xs)

Friday, April 5, 13

Case statement matching against the list “numbers”.

fun sum(numbers) = case numbers of [] => 0 x::xs => x + sum(xs)

Friday, April 5, 13

First pattern

fun sum(numbers) = case numbers of [] => 0 x::xs => x + sum(xs)

Friday, April 5, 13

Second pattern

fun sum(numbers) = case numbers of [] => 0 x::xs => x + sum(xs)

Friday, April 5, 13

So let’s call it!

Friday, April 5, 13

val numbers = [1, 6, 8]

sum(numbers)

Friday, April 5, 13

sum([1, 6, 8]) = case numbers of

[] => 0x::xs => x + sum(xs)

Friday, April 5, 13

What pattern does numbers match?

Friday, April 5, 13

x::xs => x + sum(xs)

1::[6, 8] => 1 + sum([6, 8])

Friday, April 5, 13

x::xs => x + sum(xs)

6::[8] => 1 + 6 + sum([8])

Friday, April 5, 13

x::xs => x + sum(xs)

8::[] => 1 + 6 + 8 + sum([])

Friday, April 5, 13

sum([]) = case numbers of

[] => 0x::xs => x + sum(xs)

Friday, April 5, 13

What pattern does numbers match?

Friday, April 5, 13

[] => 1 + 6 + 8 + 0

15

Friday, April 5, 13

Pretty easy, and could be implemented in roughly the same amount of characters with

“if-then-else”

Friday, April 5, 13

(* Note:

hd(list) takes the first element of list

tl(list) takes all other elements of list

*)

fun sum(numbers) =

if numbers = [] then 0

else hd(numbers) + sum(tl(numbers))

Friday, April 5, 13

Friday, April 5, 13

So maybe you’re not convinced...

Let’s try an example that’s even more interesting with pattern matching.

Friday, April 5, 13

Who knows FizzBuzz?

Friday, April 5, 13

// `~` in Rust is for allocating memory

fn main() { for int::range(1, 101) |num| { io::println( match (num % 3, num % 5) { (0, 0) => ~"FizzBuzz", // must come first (0, _) => ~"Fizz", (_, 0) => ~"Buzz", (_, _) => int::str(num) // must come last } ); }}

// Source: Lindsey Kuper’s FizzBuzz revisited (http://composition.al/blog/2013/03/02/fizzbuzz-revisited/)

Friday, April 5, 13

The real heart of the function:

match (num % 3, num % 5) { (0, 0) => ~"FizzBuzz", // must come first (0, _) => ~"Fizz", (_, 0) => ~"Buzz", (_, _) => int::str(num) // must come last }

Friday, April 5, 13

That’s nice and all, but there’s more...

Friday, April 5, 13

Both SML and Rust are strongly typed, and they let us define our own types.

We can then match against those types.

Friday, April 5, 13

enum Remainder { zero, other(NonZeroRem)}

enum NonZeroRemainder { one, two, three, four}

Friday, April 5, 13

fn int_to_rem(num: int) -> Remainder {

match num { 0 => zero, 1 => other(one), 2 => other(two), 3 => other(three), 4 => other(four), _ => fail }}

Friday, April 5, 13

fn main() { for int::range(1, 101) |num| { io::println( match (int_to_rem(num % 3), int_to_rem(num % 5)) { (other(_), other(_)) => int::str(num), (zero, other(_)) => ~"Fizz", (other(_), zero) => ~"Buzz", (zero, zero) => ~"FizzBuzz" } ); }}

Friday, April 5, 13

Again the heart of the function: match (int_to_rem(num % 3), int_to_rem(num % 5)) { (other(_), other(_)) => int::str(num), (zero, other(_)) => ~"Fizz", (other(_), zero) => ~"Buzz", (zero, zero) => ~"FizzBuzz" }

Friday, April 5, 13

Pattern Matching is powerful.

It allows us to easily change FizzBuzz

Friday, April 5, 13

match (int_to_rem(num % 3), int_to_rem(num % 5)) { (other(two), other(one)) => ~"Zot", (other(x), other(y)) => int::str(x/y), (zero, other(_)) => ~"Fizz", (other(x), zero) => ~"Buzz" + int::str(x), (zero, zero) => ~"FizzBuzz" }

Friday, April 5, 13

Friday, April 5, 13

So how about Pattern Matching in Ruby?

Friday, April 5, 13

pattern-match

github.com/k-tsj/pattern-match

Friday, April 5, 13

require 'pattern-match'

match(object) do with(pattern) do ... end with(pattern) do ... end ...end

# patterns are binded variables available in block

Friday, April 5, 13

x = match(0) do with(String) { “It’s a String!” } with(Fixnum) { “It’s a Fixnum!” } end

print x #=> “It’s a Fixnum!”

Friday, April 5, 13

require 'pattern-match'

1.upto(100) do |n|

match([(n % 3),(n % 5)]) do

with(_[0,0]) { puts "FizzBuzz" }

with(_[0,_]) { puts "Fizz" }

with(_[_,0]) { puts "Buzz" }

with(_[_,_]) { puts n }

end

end

Friday, April 5, 13

It allows for some cool things!

Friday, April 5, 13

require 'pattern-match'

match([1, "2", 3.0, "four"]) do

with(_[a & 1, b & Or(Float, String), c & Not(Fixnum), d])

with(_[a & Fixnum, b & Not(String), c, d & Not(“four”)])

end

end

Friday, April 5, 13

Friday, April 5, 13