Ruby
- Created by Yukihiro Matsumoto in mid-1990’s
- Original conversation in 1993
- First version in 1995
- Goal was to make a true OO scripting language
- OO system designed into language from the beginning, not tacked on later (like PHP, perl, JavaScript, etc.)
-
Also one of the first non-functional programming languages to bring in functional aspects.
- Why Ruby in CIS343
- As of November 2022, 7 of the top 10 languages on the Tiobe index have a very strong C pedigree. (The other three were Assembly, SQL, and VB). Ruby provides an opportunity to see what a language might look like that isn’t heavily influenced by C.
- JavaScript now has many of the same features as Ruby, but for the most part they were added after ES6 came out in 2016. Ruby allows us to compare what features look like when derived from “first principles” as opposed to having to “fit in” with an existing language.
- Ruby has a couple interesting features you might not see in other languages
- Mixins
- fully open classes.
- When I first learned Ruby around 2008, it provided the best of both worlds:
- OO when it is helpful
- functional when it is helpful
- Today many other languages now offer these features:
- Javascript
- C#
- Java (albeit poorly)
- Ruby has a different way of writing code. This is called “idiomatic”
Ruby. Although you can write familiar Java-style code,
- You will miss out on opportunities for better code
- People might laugh at you.
- Key Ruby ideas
-
Basics are not too different from what you’ve seen before:
puts "Hello, World" x = 5 y = 6 z = x + y puts "5 + 6 = #{z}" if z % 2 == 0 puts "#{z} is even" else puts "#{z} is odd" end
- variables don’t need type
- variables don’t need
$
- variables use underscores (e.g.,
multi_word_variable_name
) not “camel-case” (e.g.,doNotNameVariablesLikeThis
) - Can interpolate variables into strings using the
#{}
construct (like${}
in JavaScript)-
Interpolation is done in double quotes only:
q = 44 puts 'No Interpolation in single quotes: #{q}'
-
Can place code inside brackets
x = 10 y = 15 puts "#{x} + #{y} = #{x + y}" name = 'bob' puts "Hello, #{name.capitalize}"
-
- Symbols and Hashes
- Symbols are like immutable strings.
- They are typically used wherever you would use a constant string and/or enum in Java or C.
- method parameters that select options by name
build_distribution(:uniform)
orbuild_distribution(:normal)
- passing method and/or variable name as parameters (you’ll see examples of this in Rails.)
- keys for hashes (i.e., associative arrays)
- method parameters that select options by name
- Hashes are associative arrays: arrays where the index doesn’t have to be an integer.
-
Keys for hashes can be anything; but, symbols are most common
# The "key: value" syntax here is a short-cut for initializing hashes. # The old syntax was ":key => value" my_dog = { name: "Spot", age: 4, weight: 14 } # Add a year to the age # Notice the use of a symbol for the hash key. my_dog[:age] += 1 puts "My dog, #{my_dog[:name]} is now #{my_dog[:age]} years old." # Sometimes you will want an empty hash to fill later empty_hash = {} empty_hash[:january] = 31 empty_hash[:feb] = 28
-
- Classes and methods
- Constructor is always named
initialize
- Instance variables begin with
@
and do not need to be explicitly declared. attr_reader
andattr_accessor
- use
self
instead of this - Method parameters do not need types
- Last expression of a method becomes its return value
- Don’t use explicit
return
statement unless necessary.
- Don’t use explicit
- Constructor is always named
-
class Stats
# constructor is named "initialize"
def initialize
# instance variables begin with "@"
@values = [] # create an empty array
@sum = 0
@sum_sq = 0
end
# Notice that parameters don't need a type
def add(value)
@values << value
@sum += value
@sum_sq += value * value
end
def mean
# value of last expression becomes return value for the method
@sum.to_f / @values.count # to_f is "to float"
end
def median
sorted_values = @values.sort
mid = @values.count / 2
# notice that the if statement takes the value of the
# last line of the block that is run
if (@values.count % 2 == 0)
(@values[mid] + @values[mid - 1]) / 2.0
else
@values[mid]
end
end # method median
end # class Stats
s1 = Stats.new
s1.add(8)
s1.add(6)
s1.add(7)
puts "Mean is #{s1.mean}. Median: #{s1.median}"
s1.add(5)
puts "Mean is #{s1.mean}. Median: #{s1.median}"
Code blocks
- It is often helpful to be able to pass blocks of code (or methods) as parameters to other methods.
-
Think about the selection sort algorithm
def traditional_selection_sort(values) # Don't write for loops this way! Use the idiomatic values.each I'll show you in a minute. for i in 0..(values.count - 1) min_loc = i for j in (i + 1)..(values.count - 1) # It would be nice to make the "<" a parameter so you can # compare objects using any algorithm. min_loc = j if values[j] < values[min_loc] end # swap values[i] and values[min_loc] temp = values[i] values[i] = values[min_loc] values[min_loc] = temp end values end p traditional_selection_sort([8, 6, 7, 5, 3, 0, 9])
- The code above will only sort numbers in ascending order.
- Sorting in descending order requires cutting-and-pasting the code and changing one character ‘<’ to ‘>’
- The code to sort more complex objects (e.g., Students by GPA) is 99% the same.
- How would we modify the code to sort Students?
- Code blocks allows us to use one method for storing and pass in the comparison algorithm.
-
def selection_sort(values)
# Don't write for loops this way! Use the idiomatic values.each I'll show you in a minute.
for i in 0..(values.count - 1)
min_loc = i
for j in (i + 1)..(values.count - 1)
min_loc = j if yield(values[j], values[min_loc])
end
# swap values[i] and values[min_loc]
temp = values[i]
values[i] = values[min_loc]
values[min_loc] = temp
end
values
end
# Sorted low to high
p selection_sort([8, 6, 7, 5, 3, 0, 9]) { |a, b| a < b}
# Sorted high to low
p selection_sort([8, 6, 7, 5, 3, 0, 9]) { |a, b| a > b}
# Sort dogs by age
dog1 = {name: 'Fido', age: 14, weight: 6}
dog2 = {name: 'Spot', age: 4, weight: 12}
dog3 = {name: 'Rover', age: 12, weight: 22}
p selection_sort([dog1, dog2, dog3]) {|a, b| a[:age] < b[:age]}
- Ruby makes heavy use of the block syntax.
- Most loops use it.
- Don’t do this:
for i in 0..(array.count - 1)
. You will get laughed at. - Idiomatic ruby uses blocks:
array.each { |i| ... }
- Don’t do this:
- In the rare case you also need the loop index, use
each_with_index
array.each_with_index { |item, index| puts "Item number #{index} is #{item}"}
- Use
{}
syntax when block fits on one line. -
Use
do-end
syntax when block needs more than one line:array.each_with_index do |item, index| puts "Item number #{index} is #{item}" end
- The
map
method converts one array into another:
array = [1, 2, 3, 4, 5, 6, 7]
squared = array.map { |i| i * i }
p squared
names = [
{ first: 'George', last: 'Washington' },
{ first: 'John', last: 'Adams' },
{ first: 'Thomas', last: 'Jefferson' },
{ first: 'James', last: 'Madison' },
{ first: 'James', last: 'Monroe' }
]
full_names = names.map { |i| "#{i[:last]}, #{i[:first]}"}
p full_names
- Open classes
-
You can add methods to classes
class String def first self[0] end end puts "Hello".first
- Can also add methods to specific objet instances.
- Rails does this a lot; but,
- Don’t overuse it.
-
- Misc.
- There is no
++
or--
. Need to use+= 1
- Method names are allowed to end in ‘?’ and ‘!’
- Characters have no semantic value; but, by convention
- ’?’ methods used for methods that ask questions:
nil?
,has_key?
- ’!’ methods used for methods that modify an object
sort
vssort!
- ’?’ methods used for methods that ask questions:
- one-line
if
blocks can be written like this:return 6 if val > 10
- When debugging
p some_object
usually prints the object in a useful manner.some_object.inspect
will return the “pretty” output as a string.
- It is common to chain methods with blocks together:
array.map { |i| i*i}.select{ |j| j % 2 == 0}
- Built-in objects tend to come with a lot of “nice to have” features
- e.g.,
first
andlast
in arrays.
- e.g.,
Array
comes with a lot of useful methods:- API
select
include?
compact
count
first
last
select
max
min
push
pop
reject
reverse
slice
uniq
- There is no
- Sample Ruby files:
- For more practice
Other interesting Ruby features
- Everything is an object (e.g., even integers)
- You can overload operators.
- Open classes
- Add a method to an individual object
- Add a method to a class
- (You can do this in JavaScript also)
- Both positional and named parameters
- Mixins
attr_reader
,attr_accessor