Ruby for Python People -- (Ben's 3 bits) ====================== This is Ben's quickie summary of Ruby, mostly ripped and compressed from the book "Programming Ruby 3.3", by Rappin and Thomas, Jan 2024. I plan to present this mostly as a diff against Python, because the two languages are VERY similar. META DIFFS ---------- - Ruby is hyper object-oriented; EVERYTHING is an object, just like in Java. It has no "basic types" like ints or strings -- even those are objects too. Absolutely everthing has a unique object_id property. - Because of the extreme object-orientation, there's less of use of libraries to things like math, string manipulation, or regular expressions. Instead, the functionalities are often living in built-in methods of the object types. - To help with readability, Ruby uses specific naming conventions for variables and functions that instantly tell the variable's scope -- starting names with Capital, smallcase, @, @@, $, etc. - Ruby has a powerful "Blocks" feature -- similar to lambdas, allowing chunks of code to be treated as data, and passed as arguments to functions. - 'if' and 'while' control flows exist, but instead of 'for', Ruby tends to use iteration methods that are built into objects (!) Execution of Ruby ----------------- Ruby is an interpreted language with a REPL, just like perl, python, etc. It's EXTREMELY similar to Python, but has some large m Standard ways to execute: - Start the REPL interpreter (interactive ruby): 'irb' - Shell scripts start with '#!/usr/bin/env ruby' - It's common to have multiple versions of Ruby coexist on an OS. There are various "version manager" tools that lock down ruby programs to particular ruby versions, e.g. 'rbenv' that puts shims into a project's ./.ruby_version file If args are passed on commandline to script, they show up in array 'ARGV'. To load a library at top of a .rb file: use "require" (instead of "import"). Formatting ---------- Like most languages, it IGNORES WHITESPACE. Logical blocks are defined by (1) logically inferring the start of the block (e.g. after an 'if' statement) (2) explicitly declaring the end of a block with 'end' keyword. def foo(x) blah bloo blort end Convention is to indent by 2 spaces, but it's only a convention. Typical per-line # comments like python. Multi-line comment looks like this: =begin blah blah blah bloo blort blob =end Also, some single-line comments are MAGIC if they come at top of file, before Ruby code, and invoke interpreter behaviors. Typically of form # directive:value Printing -------- puts "Hello" puts("Hello") # equivalent There are two main print functions. - puts() writes strings to stdout. If the thing is a string, great. If not, then it tries to convert the thing (object) into some sort of string using a default .to_s() method: it writes "#", the name of the object's class and it's unique object id. Ugly. One can override to_s() in a class to make pretty-printing look the way you want for a class instance. - p() is a different option: for an object, it calls .inspect(), which produces a string representation useful for debugging (e.g. shows instance variable values). Naming Conventions! ------------------- Purely conventions, but used everywhere. The name of the item indicates its scoping. Presumably this makes code more readable? foo # local variable Foo # name of a class $foo # global variable FOO # constant @@foo # class variable @foo # instance variable 1 Multiword variables tend to use underscore, e.g. zip_code Multiword class name tend to use CamelCase. Functions --------- 'def' to define... can return data with 'return' keyword. def foo(arg1, arg2, arg3) blah bloo return blort # or just 'blort' would cause the value of blort to be returned end Note that 'return' is OPTIONAL. If not present, the value of the *last expression* is returned. This is used heavily by Ruby programmers. Strings & Symbols ----------------- Unlike python, strings are MUTABLE in Ruby. 'literal string' # single quotes treats all characters literally "The fruit is #{fruit}\n" # double quotes allows substitution, manipulation If you want something like an IMMUTABLE string, you create a "Symbol". A symbol is an immutable string created only once as a unique object, and all references to it are pointing to the same single object. Defined using colon prefix: draw_circle(:blue) draw_circle(:red) The value of :blue is just its name -- no need to declare it. Arrays ------ a = [1, 'cat', 3.14] # square brackets, just like python a[0] # returns 1 a[2] = nil # nil is an OBJECT that represents concept of nothing How about NIL?? - In python, nil means "no answer". In ruby, it's a real object returned. - Luckily, nil is still false when evaluated as a condition, e.g. if nil Special operators!! a << "foo" # appends "foo" to array a Hashes (i.e. Dictionaries) ------ Uses curly braces (like python), and uses => syntax to map keys to values. my_hash = { "foo" => "bar", "baz" => "bop" } my_hash["baz"] # returns "bop" If key doesn't exist, then nil is returned. Cool feature: can change this default behavior to return something other than nil: foo = Hash.new(0) # lack of key will return 0 instead of nil It's super common for hash keys to be symbols, useful for their immutability, e.g. my_hash = { :foo => "bar", :baz => "bop" } Because this is so common, there's a common shortcut syntax that actually looks like a python dictionary: my_hash = { foo: "bar", baz: "bop" } Control Structures ------------------ The usual stuff. The only difference with python is that ruby uses "elsif". while condition blah end if condition1 blah elsif condition2 bloo else blort end Also... we can use backwards-if syntax for one-liners. puts "oopsie" if x > 30 Regular Expressions ------------------- /slashes/ define regexps, which are objects, e.g. /ab*2o\d+/ Use the match operator =~ to run a match. Returns either starting position of match, or nil. if line =~ /Ruby|Rust/ puts "I detected the word Ruby or Rust" String and Regexp objects also have a .match?() builtin method, equivalent to =~. More popular than =~ if line.match?(/Ruby|Rust/) blah One can mutate the string and do regexp substitutions directly. newline = line.sub(/Python/, 'Ruby') # replaces 1st occurence newline2 = line.gsub(/Python/, 'Ruby') # (g)lobal replace of all occurences Basic I/O --------- Just use 'puts' and 'gets' most of the time. Defaults to stdio, but can go to other streams too. 'gets' returns next line of stdin, or nil when it hits end of input, thus it can be used as a condition: while (line = gets) print line end Blocks (CRAZY STUFF: lamba-like stuff!!) ------ NEW THING: Ruby has a powerful "Blocks" feature -- allowing chunks of code to be treated as data, and passed as arguments to functions. - DEFINE a block typically with curly braces { } or with do...end { puts "hello" } or do puts "hello" person.write end - OPTIONALLY specify parameters to a block with | | at the start of block { |arg1, arg2| puts "I love #{arg1} but not #{arg2}" } - PASS a block to any method -- do it *after* you pass normal args. - EXECUTE the block from the receiving method using the "yield" keyword. def say_stuff(greeting) puts greeting yield("moo", "cow") yield("bar", "baz"} puts "goodbye" end say_stuff("Welcome!") { |a1, a2| puts "to #{a1} and #{a2}" } - ITERATE over arrays with blocks;this is idomatic, and called ENUMERATION This instead of "for" loops (!) Standard enumeration methods are built into objects (!), like 'each', 'times', 'upto()' - 'each' is a common part of list objects a = ["foo", "bar", "baz"] a.each { |thing| puts thing } ["apple", "banana", "peach"].each { |fruit| eat_fruit(fruit} } - 'times' or 'upto' are a common part of number objects 5.times { print "wow" } # do it 5 times 3.upto(6) { |i| print i } # a specific range 3 to 6 Libraries --------- require "csv" # loads the ruby csv library require_relative "count_cows" # loads my own count_cows.rb in '.' ----------------------------------------------------------------------- MOAR DETAILS ON OBJECT FANATICISM^H^H^H^H ORIENTATION The usual stuff you see in all OO languages. - CONSTRUCTORS: Create a new class instance with .new() Constructor is always named "initialize", invoked at creation. MyClass.new(arg1, arg2) --> MyClass.initialize(...) - "ATTRIBUTES" are typicla ACCESSOR methods to get/set the value of private instance variables. Ruby has shortcuts for generating them. - Getters Class Blah def price @price end foo = blahinstance.price - Short form getter: "attr_reader" generates getters using *symbols* class Blah attr_reader :price, :color, :size ... - Setters: define a method that ends with EQUAL sign class Blah attr_reader :price def price=(new_price) @price = new_price end book.price = book.price * .75 - Short form setter: "attr_accessor" generates setters class Blah attr_reader :price, :color, :size attr_accessor :price, :color, :size ...