Tuesday 27 September 2016

My Ruby Notes

So I needed to learn Ruby. Mostly because we are going down the Puppet route at work and so I needed to work with a library that was Ruby based.

At first glance it looks very Python like but not. I thought I'd write up my notes here as I read (both for my own uses and for others in case this is useful).

So my background is as a C/C++/Java developer so I tend to look things in terms of that or the other languages (Perl, Python etc) that I have worked with. Please read these notes with that context in mind.

Comments

  • Hash (#) and multi-line wrapped in =begin and =end. The begin/end must be at the start of the line

Identifiers

  • One starting with lower case or underscore are local? (local to the scope) Weird. Otherwise what scope?
  • Can end in ! or ? or =  This is odd also
  • Object data members (instance variables) begin with @
  • Static (class variables) begin with @@
  • Variables starting with $ are global.
  • Variables in caps are constants
  • self is like self in Python or this in C++ I think.
  • nil is like Java Null
  • true/false are lower-case - seems odd as I would expect them to be FALSE and TRUE. Or $FALSE and $TRUE. Hmm
  • Long list of pre-defined $ variables. See here. Very perl like.

Literals

  • Strings quoted with single or double quotes. Can backspace escape quotes as normal
  • There is a weird % delimeter scheme where you can use other string delimeters. So you can say %{This is a string} or $(This is a string). Ok - bye bye readability.You can control interpolation of the string (see embedding expressions below) using a character after %. See here for table
  • (Interpolated string) You can embed an expression wrapped in #{ } inside a string and it will be evaluated and embedded. Not sure when it is evaluated - I assume when the string is elaborated (so probably not overly useful except for strings defined inside the code).
  • (Here documents) You can use the <<END etc syntax from shell to embed a multi-line string
  • Make an array with Array.new or []. Can initialise an array as you expect. Can use %w in front of array to initialize using spaces as delimeter instead of comma. The question is why?
  • Negative indicies index from end of array. God - readability.
  • Can specify a range with [1..5] etc. If you use three dots it doesn't include the end value. Again. readability.
  • Can have perl-like associate arrays so { "x" => 1, "y"=>2 }
  • Has concept of ranges like 0..5. Can be floats or whatever but must be consistent. Has triple equals (===) operator for testing if a value is in a range. Handy I suppose.

Operators

  • Has most of the assignment operators of C (i.e. a += 10  or a *= 10 etc)
  • No ++ or -- (like python)
  • Can use comma to do multiple assignments. Yuk. So a,b,b = 1,2,3. Also can assign an array
  • Conditional assignment is interesting x = x || "Default" - will assign if x is nil or false (but not empty it seems). Ditto x ||= "default"
  • &&= is opposite - so will assign only if NOT nil. Why?
  • nil behaves like false to logic exprs. Any non-nil value behaves like true. Very C like
  • There is an and/or as well as && and ||. The words variant has lower operator precedence so goes after assignment. Yuk - use brackets. Bye bye readability

Control

  • Conditionals have values. Ok I suppose. I think.
  • The form is if xxx (new line and statements) end. Can use then as separator for single line. What have they got against brackets?
  • There is an unless expression which is basically if not but you can't have an else
  • There is a ternary operator. Of course there is - they seemed to have boiled every other language down to the syntactically most obscure and confusing-to-read elements and included them all.
  • Can use ifs as guards x = 7 if month = 'Feb'
  • Like Moduala II and Ada (I think - God it's been a while!) There is case expression with when for each case. If you say case x you can just say when 22. If you say just case you have to say when x == 22. You say else as the default case.
  • They have while and until as loop constructs. No range iterating or iterating over arrays etc (AFAI can tell)
  •  There is a return statement.

Calls

  • Oh god make it stop. You can call functions without parenthesis. If you want to use the result inside a one-liner you have to wrap the arguments in parenthesis. So result = add 1, 2  is ok but you have to say result = add (1,2).reverse to call the reverse function on the result (ok bad example as you can't reverse an integer).
  • Method definitions are like python so def xxx(params) ... end. Params don't have types and no indication of the return value type. Methods have a value (can be nil).
  • Params can have defaults like in C++
  • Can use * in front of parameter to make a varargs call. If you use * when passing an array it expands the array as arguments (like xargs in shell I suppose)
  • You can use the ampersand & operator in front of a variable to indicate it is a code block.

Scopes

  • As above there is a local, instance, class and global scope. Code that is not in any other scope is in a local scope called the 'main' scope (basically in the scope of the 'main' object). Ok...
  • Umm there is some weirdness about 'procs' that bind to the scope you are in. Not sure what a proc is yet. If you def something it gets a new scope however... So whats a proc?

Procs Blocks and Lambdas

  • Lambda is Ruby are much like Python lambda - tiny functions you can pass around.
  • Procs are a bit different as the content of the proc runs in the same scope as the caller. For example if you use the return keyword in a proc it causes the function that called the proc to return. Also it looks like variables in the scope where the proc was called are available to the proc code. Makes life interesting if the same proc is used in multiple places. Not nice I think.
  • Blocks are confusing but as I understand it they are an anonymous proc. So you can pass a block to a function and have the function execute it. It looks like you don't pass a block as a parameter (like you would a proc or a lambda) and to call it the function uses the yield statement. Again yuk... How do you know if a function can use a block or not?

Dynamic Methods

  • Oh goody - you can dynamically create methods for just a single object
  • Worse - there are calls made on object like method_missing that can be used to dynamically create a method when the method doesn't exist. Have fun debugging that!

Classes

  • Classes must start with a capital (and be CamelCase it seems)
  • Instance variables (i.e. @thing) are ONLY accessible inside the class - so you *must* have accessors
  • There is some weirdness with modifying variables outside a def but within a class. The variables are 'class instance variables' which means they are not a class variable and not an instance variable. Essentially the class is itself an object and they are a member of that but you can't access them using @@. Basically don't do this.
  • You can call a setter method foo= which means you can say this_instance.foo = 10 and it works.
  • There is a attr_accessor for easily creating pairs of accessors.
  • You can make methods private using private keyword private: some_method
  • Private methods are accessible to child classes
  • Private are not accessible to another instance of the same class. Protected ,methods however are
  • Ruby - like most languages these days - only supports single inheritance. So I suppose you can't really define interfaces then...

Modules

  • Like Modula II modules - a namespace basically.
  • One cool thing about modules is you can 'include' them into a class. So you can define functions that refer to class variables and then you can drag those functions into multiple classes as a 'mixin'
Probably much more I can say but this is a start. Hope it's useful!