the art of ruby
DESCRIPTION
The Art of Ruby: The spiritual successor to the Origamist's Ruby Learn how to write beautiful code and how programming is a form of art.TRANSCRIPT
Matthew Mongeauhalogenandtoast
The Art of Ruby
A programmer who subconsciously views himself as an artist will enjoy
what he does and will do it better.
Donald Knuth
What is Art?
/ärt/the expression or application of human creative skill and imagination, typically in a visual form such as painting or sculpture, producing works to be appreciated primarily for their beauty or emotional power.
/ärt/the expression or application of human creative skill and imagination, typically in a visual form such as painting or sculpture, producing works to be appreciated primarily for their beauty or emotional power.
What is Art
It’s beautiful.
What is Art
It’s beautiful.It’s creative.
What is Art
It’s beautiful.It’s creative.It’s expressive.
What is Art
What is Programming
The art of writing code.
Code is Art
It’s beautiful.
Code is Art
It’s beautiful.It’s creative.
Code is Art
It’s beautiful.It’s creative.It’s expressive.
Code is Art
Writing beautiful Code
Simplicity
Code should be clear
def play puts "Welcome to the guessing game." 3.times do guess = get_guess if won? guess @won = true puts "You win!" break else puts "Wrong!" end end if !@won puts "You lost. The actual number is #{@actual_number}." endend
def play puts "Welcome to the guessing game." 3.times do guess = get_guess if won? guess @won = true puts "You win!" break else puts "Wrong!" end end if !@won puts "You lost. The actual number is #{@actual_number}." endend
def play puts "Welcome to the guessing game." play_rounds print_resultsend
Extract MethodPattern
def play print_header play_rounds print_resultsend
Single Level of Abstraction
Principle
Complexity
Balance
def play_rounds round_count.times do if correct_guess? get_guess @won = true break else puts "Wrong!" end endend
def play_rounds @won = round_count.times.detect { play_round }end
def play_round if correct_guess? get_guess true else puts "Wrong!" endend
def play_rounds @won = round_count.times.detect { play_round }end
def play_round if correct_guess? get_guess true else puts "Wrong!" # this returns nil which is falsey endend
Constraints
The more constraints one imposes, the more one frees one's self.
Igor Stravinsky
Code Constraints
Code Constraints
5 line methods
Code Constraints
5 line methods100 line classes
Code Constraints
5 line methods100 line classesNo classes in public methods
def play puts "Welcome to the guessing game." 3.times do guess = get_guess if won? guess @won = true puts "You win!" break else puts "Wrong!" end end if !@won puts "You lost. The actual number is #{@actual_number}." endend
def play print_header play_rounds print_resultsend
def play_rounds round_count.times do if correct_guess? get_guess @won = true break else puts "Wrong!" end endend
def play_rounds @won = round_count.times.detect { play_round }end
def play_round if correct_guess? get_guess true else puts "Wrong!" endend
def play_rounds @won = round_count.times.detect { play_round }end
def play_round if correct_guess? get_guess true else puts "Wrong!" # this is still bothering me endend
Breaking the Rules
Pablo
Picasso
Learn the rules like a pro, so you can break
them like an artist.
def play_round if correct_guess? get_guess true else puts "Wrong!" endend
def play_round if correct_guess? get_guess puts "Correct" true else puts "Wrong!" false endend
def play_round if correct_guess? get_guess correct_guess else incorrect_guess endend
def incorrect_guess puts "Wrong!" falseend
def correct_guess puts "Correct" falseend
def play_round if correct_guess? get_guess correct_guess else incorrect_guess endend
def incorrect_guess puts "Wrong!" falseend
def correct_guess puts "Correct" falseend
class Guess def initialize actual_number @actual_number = actual_number @guess = get_guess end
def status if correct? "Correct" else "Incorrect" end end
def correct? guess == actual_number end
private attr_reader :guess, :actual_number
def get_guess print "What is your guess: " gets.to_i endend
Extract Class
Pattern
def play_round guess = Guess.new(actual_number) puts guess.display_status guess.correct?end
Extract Class
Pattern
Extract Method
1
Extract Method Extract Class
1 2
Extract Method Extract Class Extract Gemor App
1 2 3
You Win
Performance
If you’re willing to restrict the flexibility of your approach, you can almost always do something better.
John Carmack
module Alchemist module Conversion def method_missing unit_name, *args, &block exponent, unit_name = Alchemist.parse_prefix(unit_name) if Alchemist.has_measurement?(unit_name) Alchemist.measurement self, unit_name, exponent else super( unit_name, *args, &block ) end end endend
class Numeric include Alchemist::Conversionend
module Alchemist module Conversion def method_missing unit_name, *args, &block exponent, unit_name = Alchemist.parse_prefix(unit_name) if Alchemist.has_measurement?(unit_name) Alchemist.measurement self, unit_name, exponent else super( unit_name, *args, &block ) end end endend
class Numeric include Alchemist::Conversionend
def self.parse_prefix(unit) matches = unit.to_s.match(prefix_matcher) prefix, parsed_unit = matches.captures
if prefix && si_units.include?(parsed_unit) value = prefixed_value_for(prefix.to_sym, parsed_unit) [value, parsed_unit.to_sym] else [1, unit] end end
def self.parse_prefix(unit) matches = unit.to_s.match(prefix_matcher) prefix, parsed_unit = matches.captures
if prefix && si_units.include?(parsed_unit) value = prefixed_value_for(prefix.to_sym, parsed_unit) [value, parsed_unit.to_sym] else [1, unit] end end
def self.parse_prefix(unit) matches = unit.to_s.match(prefix_matcher) prefix, parsed_unit = matches.captures
if prefix && si_units.include?(parsed_unit) value = prefixed_value_for(prefix.to_sym, parsed_unit) [value, parsed_unit.to_sym] else [1, unit] end end
def self.prefix_matcher keys = unit_prefixes.keys.map(&:to_s). sort{ |a,b| b.length <=> a.length } %r{^(#{keys.join('|')})?(.+)} end
[:googol, :yotta, :Y, :zetta, :Z, :exa, :E, :peta, :P, :tera, :T, :giga, :G, :mega, :M, :kilo, :k, :hecto, :h, :deca, :da, :deci, :d, :centi, :c, :milli, :m, :micro, :u, :nano, :n, :pico, :p, :femto, :f, :atto, :a, :zepto, :z, :yocto, :y, :kibi, :Ki, :mebi, :Mi, :gibi, :Gi, :tebi, :Ti, :pebi, :Pi, :exbi, :Ei,
:zebi, :Zi, :yobi, :Yi]
/^(googol|yotta|femto|zetta|zepto|micro|milli|centi|hecto|yocto|exbi|giga|tebi|mega|pebi|kilo|atto|tera|kibi|deca|yobi|deci|pico|nano|gibi|zebi|mebi|peta|exa|Ki|Mi|Gi|Zi|da|Ei|Ti|Pi|Yi|z|a|y|f|p|n|m|c|d|
h|k|M|G|T|P|E|Z|Y|u)?(.+)/
Some people, when confronted with a problem, think "I know, I'll
use regular expressions." Now they have two problems.
Jamie Zawinski
I thought
"I know, I'll use regular expressions."
I now had two problems
but
def self.prefix_matcher @prefix_matcher ||= begin prefix_keys = unit_prefixes.keys.map(&:to_s). sort{ |a,b| b.length <=> a.length } %r{^(#{prefix_keys.join('|')})?(.+)} endend
2.0
1.9
Keep existing interface
Constraints
Keep existing interfaceNo method_missing
Constraints
Keep existing interfaceNo method_missingIncrease performance
Constraints
module Alchemist
def self.setup category = nil if category load_category category else load_all_categories end end
private
def self.load_all_categories library.load_all_categories end
def self.load_category category library.load_category category endend
module Alchemist class ModuleBuilder def initialize category @category = category end
def build Module.new.tap do |category_module| category_module.class_eval %(def self.inspect() "#<Module(#{category})>" end) category_module.class_eval category_methods end end
private attr_reader :category
def library Alchemist.library end
def category_methods unit_names.map do |name| %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name) end.join("\n") end
def unit_names library.unit_names(category) end
def prefixes_with_value(name) if library.si_units.include?(name.to_s) library.unit_prefixes else [] end end
def prefixed_methods(name) prefixes_with_value(name).map do |prefix, value| %(define_method("#{prefix}#{name}") { Alchemist.measure self, :#{name}, #{value} }) end.join("\n") end
module Alchemist class ModuleBuilder def initialize category @category = category end
def build Module.new.tap do |category_module| category_module.class_eval %(def self.inspect() "#<Module(#{category})>" end) category_module.class_eval category_methods end end
module Alchemist class ModuleBuilder def initialize category @category = category end
def build build_module do |category_module| define_inspect_method(category_module) define_unit_methods(category_module) end end
[success_kid.jpg]
the c-levelthe c-level#include <ruby.h> #include <string.h>
VALUE cParenParser;
VALUE paren_parser_parse(VALUE self, VALUE str) { const char *c_str = RSTRING_PTR(str); char *temp = (char *)malloc(sizeof(char) * RSTRING_LEN(str) + 1); int temp_pos = 1; int c_str_pos = 0; temp[0] = c_str[0]; while(c_str[c_str_pos++] != '\0') { if(temp_pos > 0 && temp[temp_pos-1] == ')' && c_str[c_str_pos] == '(') { temp_pos--; } else { temp[temp_pos++] = c_str[c_str_pos]; } } temp[temp_pos] = '\0';
return rb_str_new2(temp);}
void Init_paren_parser() { mParenParser = rb_define_module("ParenParser"); cParser = rb_define_class_under(mParenParser, "Parser", rb_cObject); rb_define_method(cParser, "parse", paren_parser_parse, 1);}
How to be an artist
Have a toolbox
Be inspired by others
Surround yourself with talented artists
Surround yourself with talented artists
Use well known techniques
Use well known techniques
Measure improvement
PULLREVIEW
Have feelings
Know when to break the rules
Have fun.
Primelearn.thoughtbot.com/prime
THISISMETIS.COM
?