Ruby for Java Developers

Post on 21-Mar-2016

80 views 0 download

description

Ruby for Java Developers. Neal Ford Architect Thought Works www.nealford.com www.thoughtworks.com nford@thoughtworks.com Blog: memeagora.blogspot.com. Questions, Slides, and Samples. Please feel free to ask questions anytime The slides and samples will be available at www.nealford.com - PowerPoint PPT Presentation

Transcript of Ruby for Java Developers

Ruby for Java Developers

Neal FordArchitect

ThoughtWorkswww.nealford.com

www.thoughtworks.comnford@thoughtworks.com

Blog: memeagora.blogspot.com

2

Questions, Slides, and Samples

• Please feel free to ask questions anytime• The slides and samples will be available at

www.nealford.com• I’ll show that address again at the end

3

What This Session Covers:

• The Ruby Philosophy• Ruby syntax• Classes and objects in Ruby• Closures• Mixins• Networking• Threading• Test-first coding in Ruby• Ruby as a scripting language• Ruby as a web language

4

What is Ruby?

• From www.ruby-lang.org:• Ruby is the interpreted scripting language for quick and easy

object-oriented programming. • It has many features to process text files and to do system

management tasks (as in Perl). • It is simple, straight-forward, extensible, and portable.

• Ruby has:• Simple syntax, partially inspired by Eiffel and Ada• Exception handling features, like Java or Python• operators that are syntax sugar for the methods. You can

redefine them easily

5

Features of Ruby

• Ruby is a complete, full, pure object oriented language. • All data in Ruby is an object, in the sense of Smalltalk: no

exceptions. • Example: In Ruby, the number 1 is an instance of class Fixnum.

• Ruby's OO is carefully designed to be both complete and open for improvements. • Example: Ruby has the ability to add methods to a class, or

even to an instance during runtime. A• An instance of one class *can* behave differently from other

instances of the same class.

6

Features of Ruby

• Ruby features single inheritance only, *on purpose*. • But Ruby knows the concept of modules (called Categories in

Objective-C). • Modules are collections of methods. • Every class can import a module and so gets all its methods for

free.

• Ruby features true closures. • Not just unnamed function, but with present variable bindings.

• Ruby features blocks in its syntax (code surrounded by '{' ... '}' or 'do' ... 'end'). • These blocks can be passed to methods, or converted into

closures.

7

Features of Ruby

• Ruby features a true mark-and-sweep garbage collector. • It works with all Ruby objects. • You don't have to care about maintaining reference counts in

extension libraries.

• Integers in Ruby can (and should) be used without counting their internal representation. • There *are* small integers (instances of class Fixnum) and

large integers (Bignum), but you need not worry over which one is used currently.

• If a value is small enough, an integer is a Fixnum, otherwise it is a Bignum. Conversion occurs automatically.

8

Features of Ruby

• Ruby needs no variable declarations. • It uses simple naming conventions to denote the scope of

variables.

• Ruby can load extension libraries dynamically if an OS allows.

• Ruby features OS independent threading• Ruby is highly portable: it is developed mostly on Linux,

but works on many types of UNIX, DOS, Windows 95/98/Me/NT/2000/XP, MacOS, BeOS, OS/2, etc.

var_name => local variable@var_name => instance variable@var_name => global variable

9

Obtaining Ruby

• The main Ruby site: www.ruby-lang.org• For Windows, the One-Click Ruby Installer project on

RubyForge: http://rubyinstaller.rubyforge.org/wiki/wiki.pl• If you have Cygwin, you can choose Ruby as one of the

language options• There is a specific cygwin-ruby version, optimized for Cygwin

• The English reference manual is very outdated• However, http://www.ruby-doc.org/ has up to date

RubyDocs and other resources• The Pick-Axe book (shown at the end)

10

Classes and Objects in Ruby

class Employee def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end def to_s "Name is #{@name}, salary is #{@salary}, “

+ "hire year is #{@hire_year}" end def raise_salary_by(perc) @salary += (@salary * 0.10) endend

11

Inheritance

class Manager < Employee def initialize(name, salary, hire_year, asst) super(name, salary, hire_year) @asst = asst end def to_s super + ",\tAssistant info: #{@asst}" end def raise_salary_by(perc) perc += 2005 - @hire_year super(perc) endend

12

Tying It Together

def show(emps) emps.each { |e| puts e }end

employees = Array.newemployees[0] = Employee.new("Homer", 200.0, 1995)employees[1] = Employee.new("Lenny", 150.0, 2000)employees[2] = Employee.new("Carl", 250.0, 1999)employees[3] = Manager.new("Monty", 3000.0, 1950,

employees[2])

show(employees)

employees.each { |e| e.raise_salary_by(10) }puts "\nGive everyone a raise\n\n"show employees

13

Class Members

class Employee @@num_employees = 0; def Employee.num_employees @@num_employees end def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year @@num_employees += 1 end # . . .

show employees

puts "Number of employees in the company: " + Employee.num_employees.to_s

14

Properties

class Employee def name @name end def salary @salary end def hire_year @hire_year end # . . . emp = Employee.new("Homer", 100.0, 2004)printf "Name: %s\n", emp.nameprintf "Salary: %d\n", emp.salaryprintf "Hire Year: %s\n", emp.hire_year

15

attr_readerclass Employee attr_reader :name, :salary, :hire_year def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end

# . . .

emp = Employee.new("Homer", 100.0, 2004)printf "Name: %s\n", emp.nameprintf "Salary: %d\n", emp.salaryprintf "Hire Year: %s\n", emp.hire_year

16

Writable Properties

class Employee def name @name end def name=(new_name) @name = new_name end def salary @salary end def hire_year @hire_year end # . . . emp = Employee.new("Homer", 100.0, 2004)emp.name = "Monty"printf "Name: %s\n", emp.name

17

attr_writer

class Employee attr_reader :name, :salary, :hire_year attr_writer :name, :salary, :hire_year def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end #. . .

emp = Employee.new("Homer", 100.0, 2004)emp.name = "Monty"printf "Name: %s\n", emp.name

18

Singletons

class Logger private_class_method :new @@logger = nil def Logger.create @@logger = new unless @@logger @@logger endend

puts Logger.create.object_idputs Logger.create.object_id

19

Access Control

• Ruby has 3 levels of access control

public • Called by anyone (i.e., no access control)• Default access (except for initialize, which is always private

protected • Only by objects of the defining class and its subclasses

private • Cannot be called without an explicit receiver• You cannot specify an object when using them• Can be called only in the defining class and by direct descendents within that same object

20

Private vs. Protected

• The difference is subtle• Protected

• Can be called by any instance of the defining class or its subclasses

• Private• Called only within the context of the calling object• Never possible to access another object’s private methods

directly, even if the object is the same class as the caller

21

Arrays - General Syntax

a = [ 3.14159, "pi", 99 ]a.type » Arraya.length » 3a[0] » 3.14159a[1] » "pi"a[2] » 99a[3] » nilb = Array.newb.type » Arrayb.length » 0b[0] = "second"b[1] = "array"b » ["second", "array"]

22

Arrays - Indexing

• Negative indicesa = [ 1, 3, 5, 7, 9 ]a[-1] » 9a[-2] » 7a[-99] » nil

• Pairsa = [ 1, 3, 5, 7, 9 ]a[1, 3] » [3, 5, 7]a[3, 1] » [7]a[-3, 2] » [5, 7]

23

Arrays - Ranges

a = [ 1, 3, 5, 7, 9 ]a[1..3] » [3, 5, 7]a[1...3] » [3, 5]a[3..3] » [7]a[-3..-1] » [5, 7, 9]

24

Array Assignment

a = [ 1, 3, 5, 7, 9 ] » [1, 3, 5, 7, 9]a[1] = 'foo' » [1, "foo", 5, 7, 9]a[-3] = 'bar' » [1, "foo", "bar", 7, 9]a[3] = [ 9, 8 ] » [1, "foo", "bar", [9, 8],

9]a[6] = 99 » [1, "foo", "bar", [9, 8], 9, nil,

99]

25

Array Assignment Ranges

a = [ 1, 3, 5, 7, 9 ] » [1, 3, 5, 7, 9]a[2, 2] = 'cat' » [1, 3, "cat", 9]a[2, 0] = 'dog' » [1, 3, "dog", "cat", 9]a[1, 1] = [ 9, 8, 7 ]

» [1, 9, 8, 7, "dog", "cat", 9]a[0..3] = [] » ["dog", "cat", 9]a[5] = 99 » ["dog", "cat", 9, nil, nil, 99]

26

Hashes

h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }

h.length » 3h['dog'] » "canine"h['cow'] = 'bovine'h[12] = 'dodecine'h['cat'] = 99h » {"cow"=>"bovine",

"cat"=>99, 12=>"dodecine", "donkey"=>"asinine", "dog"=>"canine"}

27

Blocks

class EmployeeList def initialize @employees = Array.new end def add(an_employee) @employees.push(an_employee) self end def delete_first @employees.shift end def delete_last @employees.pop end

28

Blocks def show @employees.each { |e| puts e } end def [](key) if key.kind_of?(Integer) @employees[key] end endend

list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))

list.showputs "Employee #1 is " + list[0].to_s

29

Iterating with for def [](key) if key.kind_of?(Integer) @employees[key] else for i in 0..@employees.length return @employees[i] if key == @employees[i].name end end return nil endend

list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))

list.showputs "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " + list["Homer"].to_s

30

Using Blocks

def [](key) if key.kind_of?(Integer) result = @employees[key] else result = @employees.find{ |anEmp| key == anEmp.name } end return result endend

puts "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " +

list["Homer"].to_s

31

The Ruby Way

def [](key) return @employees[key] if key.kind_of?(Integer) return @employees.find { |anEmp| key == anEmp.name } endend

list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))

list.showputs "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " +

list["Homer"].to_s

32

Building Iterators

def fib_up_to(max) i1, i2 = 1, 1 # parallel assignment while i1 <= max yield i1 i1, i2 = i2, i1+i2 endend

fib_up_to(1000) { |f| print f, " " }

33

Blocks for Transactions

class File def File.open_and_process(*args) f = File.open(*args) yield f f.close() endend

File.open_and_process("testfile", "r") do |aFile| print while aFile.getsend

34

Blocks for Transactions, Take 2

class File def File.myOpen(*args) aFile = File.new(*args) # If there's a block, pass in the file and close # the file when it returns if block_given? yield aFile aFile.close aFile = nil end return aFile endend

35

Closures

• Closures represent a (nameless) block of code that retains its context, even if the context is out of scope

• A closure object has code to run, the executable, and state around the code, the scope.

• You can refer to the local variables inside a closure.• Even after the function has returned, and its local scope

has been destroyed, the local variables remain in existence as part of the closure object.

• When no one refers to the closure anymore, it's garbage collected, and the local variables go away.

36

Closure Exampledef make_counter var = 0 proc do # coverts a block to a proc object

var +=1 endend

c1 = make_counterc1.callc1.callc1.call

c2 = make_counter

puts "c1 = #{c1.call}, c2 = #{c2.call}"# output: c1 = 4, c2 = 1

37

Regular Expressions

• Regular expressions are a built-in data type in Ruby

a = Regexp.new('^\s*[a-z]') » /^\s*[a-z]/b = /^\s*[a-z]/ » /^\s*[a-z]/c = %r{^\s*[a-z]} » /^\s*[a-z]/

a = "Fats Waller"a =~ /a/ » 1a =~ /z/ » nila =~ "ll" » 7

38

Extending Classes

• Classes are never closed in Ruby• You can add new methods to user or system classes• Just open the definition and start typing!class String def rot(num = 13) return self.split("").collect { |ch| if /^[a-z]$/ === ch ((ch[0] + num - 'a'[0]) % 26 + 'a'[0]).chr elsif /^[A-Z]$/ === ch ((ch[0] + num - 'A'[0]) % 26 + 'A'[0]).chr else ch end }.join("") end

alias rot13 rotend

39

Conditionals

• True in Ruby implies• Any value that is not nil or the constant false• Zero is not false• A zero-length string is not false

• Ruby includes an operator defined?• Returns nil if its argument (which can be an arbitrary

expression) is not defined• Otherwise returns a description of the argument

defined? 1 » "expression"defined? dummy » nildefined? printf » "method"defined? String » "constant"

40

Comparison Operators

Operator Meaning

== Equality

=== Equality in a case statement when clause

<=> General comparison operator, returns -1, 0, +1

<, <=, >=, >

Standard comparisons

=~ Regular expression pattern match

eql? True if receiver and argument are both the same type and have equal values1 == 1.0 >> true 1.eql?(1.0) >> false

equal? True if the receiver and argument have the same object id

41

if Expression

• General form:

if emp.name == "Homer" then handle = "slacker"elsif emp.name == "Lenny" then handle = "go-getter"else handle = "unknown"end

42

if Expression

• If you lay them out on separate lines, you can skip the then keyword

if emp.name == "Homer" handle = "slacker"elsif emp.name == "Lenny" handle = "go-getter"else handle = "unknown"end

43

if Expression

• You can use if as an expression if you want

handle = if emp.name == "Homer" then "slacker" elsif emp.name == "Lenny" then "go-getter" else "unknown" end

44

unless, Conditional Expressions

• The unless expression is a negated if statement

unless emp.salary > 2000 then raise_percentage = 0.10else raise_percentage = 0.11end

• There is also the ternary operator

raise_percentage = empl.salary > 2000 ? 0.10 : 0.11

45

If and Unless Modifiers

• Like Perl, statement modifiers allow you to attach conditional statements onto the end of a normal statement

mon,day,year = $1,$2,$3 if /(\d\d)-(\d\d)-(\d\d)/puts "a = #{a}" if debugprint total unless total == 0

while gets next if /^#/ # Skip comments parseLine unless /^$/ # Don't parse empty lines

end

46

Case Expressions

case inputLine when "debug" dumpDebugInfo dumpSymbols when /p\s+(\w+)/ dumpVariable($1) when "quit", "exit" exit else print "Illegal command: #{inputLine}"end

47

Case + Regular Expressions

case line when /name=(.*)/ puts "Name is #$1" when /salary=(.*)/ puts "Salary is #$1" when /Hire\sYear=(.*)/ puts "Hire Year is #$1"end

48

Loops

• While loop

while gets # . . .end

• Until loop

until emp.salary > 2500 employee_list.add(emp.pop)end

49

Loops as Modifiers

a *= 2 while a < 100

a -= 10 until a < 100

50

Iterators

3.times do print "knock "end# knock knock knock

0.upto(9) do |x| print x, " "end#0 1 2 3 4 5 6 7 8 9

0.step(12, 3) {|x| print x, " " } # 0, 3, 6, 9, 12

51

Exceptions

the_file = File.open(name, "w")begin # Exceptions raised by this code will # be caught by the following rescue clause while data = socket.read(512) the_file.write(data) end

rescue SystemCallError $stderr.print "IO failed: " + $! the_file.close File.delete(name) raiseend

52

ensure and else

f = File.open("testfile")begin # .. processrescue # .. handle errorelse puts "Congratulations-- no errors!"ensure f.close unless f.nil?end

53

retrybegin if @esmtp then @command.ehlo(helodom) else @command.helo(helodom) end

rescue ProtocolError if @esmtp then @esmtp = false retry else raise endend

54

Modules

• Modules allow you to group methods, classes, and constants• Provide unique namespaces• Implement mixins

• Namespaces allow you to partition your application into separate files

• Use the require keyword to reference other modules

55

Mixins

• Ruby does not support multiple inheritance• Instead, it supports mixins• You define methods within a module• You include that module into a class

56

Mixins

module Debug def who_am_i? "#{self.class.name} " + "(\##{self.object_id}): #{self.to_s}" endend

class Employee include Debug . . .endclass Manager < Employee include Debug . . .end

puts "\n\nWho are they?"puts employees[0].who_am_i?puts employees[3].who_am_i?

57

Threadsrequire 'net/http'

pages = %w( www.rubycentral.com www.nealford.com www.pragmaticprogrammer.com )threads = []for page in pages threads << Thread.new(page) { |myPage| h = Net::HTTP.new(myPage, 80) puts "Fetching: #{myPage}" resp, data = h.get('/', nil ) puts "Got #{myPage}: #{resp.message}" }end

threads.each { |aThread| aThread.join }

58

Thread Variables

• Thread has a special facility that allows thread-local variables to be created and accessed by name

• Treat the thread object as if it were a Hashcount = 0arr = []10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 }endarr.each {|t| t.join; print t["mycount"], ", " }puts "count = #{count}"

59

Testing and Rubyrequire 'test/unit/testcase'require 'Employee'

class TestEmployee < Test::Unit::TestCase def setup @emp = Employee.new("Homer", 3000, 2004) end def teardown @emp = nil end

def test_raise_salary_by @emp.salary = 2000 perc = 10; expected_salary = @emp.salary +

(@emp.salary * 0.1 * perc) @emp.raise_salary_by(10) assert(@emp.salary = expected_salary) endend

60

Testing and Ruby

class TestEmployeeList < Test::Unit::TestCase

def setup @list = EmployeeList.new @test_subject =

Employee.new("Homer", 3000, 2004) end def teardown @list = nil @test_subject = nil end def test_add @list.add(@test_subject) assert(@list.delete_last.eql?(@test_subject)) assert(@list.empty?) end

61

Testing and Rubyrequire 'test/unit/testsuite'require 'test/unit/ui/tk/testrunner'require 'test/unit/ui/console/testrunner'require 'TestEmployee'require 'TestEmployeeList'

class TestSuite_AllTests def self.suite suite = Test::Unit::TestSuite.new("HR Tests") suite << TestEmployee.suite suite << TestEmployeeList.suite return suite endend

#Test::Unit::UI::Tk::TestRunner.run(TestSuite_AllTests)Test::Unit::UI::Console::TestRunner.run(

TestSuite_AllTests)

62

Controlling Windows

require 'Win32API'require 'win32ole'

class DailyLogs private @@Home_Dir = "c:\\MyDocuments\\" def get_doc_list docs = Array.new docs << "im\\2005 Weekly Workout Planner.xls" docs << "im\\2005 Workout Log.xls" docs << "trackers\\Master Task List.xls" end

63

Controlling Windows

public def open_daily_logs excel = WIN32OLE.new("excel.application") workbooks = excel.WorkBooks excel.Visible = true get_doc_list.each { |f| workbooks.Open(@@Home_Dir + f, true) } excel.Windows.Arrange(7) end end

DailyLogs.new.open_daily_logs

64

Java vs. Ruby: Java, City.javapublic class City { private String name; private String symposiumName; private int showId;

public City(final String name, final String symposiumName, final int showId) { this.name = name; this.symposiumName = symposiumName; this.showId = showId; }

public String getName() { return name; }

public String getSymposiumName() { return symposiumName; }

public int getShowId() { return showId; }}

65

Java vs. Ruby, Harvester.javapublic class Harvester { public static final String WEB_SITE = "http://www.nofluffjuststuff.com"; public static final String PAGE_NAME = "/show_topics.jsp?showId="; private static final String NAME_REGEX = "Neal\\sFord";

public Harvester() { int count; System.out.println("No Fluff, Just Stuff sessions:"); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); try { for (Iterator<City> cityItr = createCityList().iterator(); cityItr.hasNext();) { count = 0; City city = cityItr.next(); StringBuffer content = getWebContent(city); Matcher matcher = generateRegexForMatch(content); while (matcher.find()) { count++; } System.out.println(city.getName() + " = " + count); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

66

Java vs. Ruby, Harvester.java private Matcher generateRegexForMatch(final StringBuffer content) { Pattern pattern = Pattern.compile(NAME_REGEX); return pattern.matcher(content); }

private StringBuffer getWebContent(final City city) throws IOException {

URL url = new URL(WEB_SITE + PAGE_NAME + city.getShowId()); HttpURLConnection http = (HttpURLConnection) url.openConnection(); InputStream is = http.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; StringBuffer content = new StringBuffer(1000); while ((line = reader.readLine()) != null) { content.append(line); } return content; }

private List<City> createCityList() { List<City> cityList = new ArrayList<City>(10); cityList.add(new City("Salt Lake", "Salt Lake", 16)); cityList.add(new City("Pacific Northwest", "Seattle", 32)); return cityList; }}

67

Java vs. Ruby, harvester.rbrequire 'net/http'

class City def initialize(symposium_name, city_name, show_id) @city_name = city_name @symposium_name = symposium_name @show_id = show_id end attr_reader :city_name, :symposium_name, :show_idend

def check_city(http, url, city) count = 0 http.request_get(url) { |response| response.read_body { |body| body.scan(/Neal\sFord/) { count += 1 } } } printf("%-20s = %d\n", city, count)end

68

Java vs. Ruby, harvester.rb

Page_Name = '/show_topics.jsp?showId='cities = Array.newcities << City.new('Salt Lake', 'Salt Lake', 16)cities << City.new('Pacific', 'Seatle', 32)

http = Net::HTTP.new('www.nofluffjuststuff.com', 80)

puts "No Fluff, Just Stuff sessions:"puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"cities.each { |city| check_city(http, Page_Name + city.show_id.to_s, city.city_name)}

69

Resources:

The “Pick-Axe” Book

Programming Ruby: The Pragmatic Programmer’s Guide

Dave Thomas, Andy Hunt

Ruby in a Nutshell

Yukihiro Matsumoto, David L. Reynolds (Translator)

70

Resources:

Ruby Developer's Guide

Michael Neumann, Jonothon Ortiz, Robert Feldt, Lyle Johnson

The Ruby Way

Hal Fulton, Guy Hurst

Neal Fordwww.nealford.com

nford@thoughtworks.commemeagora.blogspot.com

Questions?

Samples & slides at www.nealford.com