iPhone App Directory
RPG Maker VX
 
Gallery Members Search Calendar Help


Welcome Guest ( Log In | Register )

Collapse

>Announcement

The 2nd Quarter Contest has arrived! Go check it out in the Community Announcements now!
 
Reply to this topicStart new topic
> Cool Ruby Tricks
BigEd781
post Sep 30 2009, 11:30 PM
Post #1


No method: 'stupid_title' found for 'nil:NilClass`
Group Icon


Type: Coder
Alignment: Chaotic Good




Inspired today by my discovery that Ruby natively supports continuations, I have decided to start a thread which will serve only to showcase the cool 'tricks' that you can do in Ruby. Anyone is allowed (encouraged) to contribute to this thread, but spamming will not be tolerated and posts deemed as spam will be deleted. Also, Mithran, Mr. A, OrigWij, and myself will be the arbiters of what constitutes a 'cool trick'. If your post gets deleted, don't take it personally, it just does not meet our standard of 'cool', i.e., it is common knowledge.

OK, so the rules are simple:

1. Post a complete code sample that can be pasted into irb and run with no modifications.
2. Post an explanation of what the code does and how/why it works.
3. If you are using language features from a new version of Ruby please let us know.
4. You may comment or ask questions about a code sample, but please limit these as much as possible. Questions are great, but if you only wish to comment you should give us a 'cool trick' in turn.

Alright, I'll start this off:

CODE
class Foo

  def bar
    catch :break do
      puts 'starting...'
      callcc do |@continuation|
        puts 'pausing...'
        throw :break
      end
      puts 'finished'
    end
  end

  def continue
    @continuation.call
    puts 'this line will never be executed'
  end

end


CODE
# usage
f = Foo.new
f.bar
# starting...
# pausing...

f.continue
# finished


This is called a continuation. A concept often used in functional programming languages, this allows us to save the state of a function for non-local execution at a later time. Using this, we can pass around methods as first class objects which hold state of their own.

When we start the method 'bar', we set up a 'catch' block and associate a symbol with it:

CODE
class Foo
  def bar
    catch :break do


When a 'throw <symbol>' statement is encountered inside of the catch block, the program execution zips up the call stack looking for a matching symbol. When found, the program execution begins again at the end of the catch block.

The next interesting bit is this line:

CODE
callcc do |@continuation|


'callcc' is a method defined in the Kernel module (Kernel#calcc). What it does is a bit abstract, so don't feel bad if you do not get it the first time you read this. Here is the official definition:

QUOTE
callcc {|cont| block } => obj

Generates a Continuation object, which it passes to the associated block. Performing a cont.call will cause the callcc to return (as will falling through the end of the block). The value returned by the callcc is the value of the block, or the value passed to cont.call. See class Continuation for more details. Also see Kernel::throw for an alternative mechanism for unwinding a call stack.


So, what does this mean? We call the method which generates for us a Continuation object (@continuation) and then pass in a block of code. This continuation object is passed to our block, but we don't do anything with it yet. When we call 'throw :break', we unwind the call stack, preventing the callcc method from returning, and its state is saved in the continuation object for use at a later time. The last statement in our method:

CODE
puts 'finished'


Will not be called due to out 'throw' statement.

So, we want to finish our method. We do so by calling our second method, "continue":

CODE
def continue
  @continuation.call
  puts 'this line will never be executed'
end


All that method does is use the saved continuation object (@continuation) to re-enter our method where we left off. Because the callcc will return at the end of the code block, the 'puts' statement will never be executed.

This is a rather contrived example, but continuations can be used for all sorts of neat (read: hard to understand) things in programming. Hope to see more posts soon!


--------------------
My blog - It's awesome, I assure you
QUOTE
While sloppy writing does not invariably mean sloppy thinking, we've generally found the correlation to be strong -- and we have no use for sloppy thinkers. If you can't yet write competently, learn to. - Eric Raymond

---
My awards for being so awesome
Spoiler:
Go to the top of the page
 
+Quote Post
   
OriginalWij
post Oct 1 2009, 02:27 AM
Post #2


I script my own reality ...
Group Icon


Type: Coder
Alignment: Chaotic Evil




I have 2 little snippets that have cool functions ....


1) When using aliases, add "unless $@" to the end of it like so:
CODE
alias new_method_name old_method_name unless $@
def old_method_name
  p "same method"
end

This will prevent those pesky "stack overflow" errors when F12 is pressed.
What it does is not allow the method to be re-aliased when the system is soft-reset by F12.
Note by BigEd781: I would just add that what we are really checking here is whether $@, an array of exceptions, is nil or not. If it is not nil, an exception was thrown up the stack and we o not re-alias the method.


2) The infamous === ... thats right ... three equals in a row.
It's used to compare a variable to a range. For example:
CODE
if (13..42) === item.id
  p item.id
end
If the item's ID is in the range of 13 to 42, it will print said ID.

The range can also use "..." to not include the maximum iterator:
CODE
if (10...data.size) === index
  p data[index]
end
If the index is in the range of 10 to data.size - 1, it will print that part of the "data" array.

!== does the same thing but checks to see if the variable is NOT in the range
CODE
if (x..y) !== counter
  p counter
end
If the counter is NOT in the range of x to y, it will print the counter

NOTE: The range must ALWAYS be to the left of the === :
CODE
if (12..69) === index     <--- correct
if index === (12..69)     <--- incorrect


--------------------




Go to the top of the page
 
+Quote Post
   
Mithran
post Oct 1 2009, 04:54 AM
Post #3


Scripter
Group Icon


Type: Coder
Alignment: True Neutral




Deep Cloning an object

Objects can be cloned by calling the object#dup or object#clone method on them, as such. These methods, however, produce only a shallow clone of an object, meaning that while the object itself is considered a new object, all of the object's properties or instance variables are still considered the same objects:

Spoiler:
CODE
class AnObj
  attr_accessor :an_array
  attr_accessor :an_integer
  def initialize(n, m)
    @an_array = [n]
    @an_integer = m
  end
end

obj1 = AnObj.new(1, 1)
obj2 = obj1.clone # obj2 is now a different object from object 1
obj2.an_array[0] = 55 # element assignment was called on the array object held by an_array.  This is the same array still referenced by obj1, so element 0 of an_array in obj1 will also change
obj2.an_integer = 55

p obj1.an_array # [ 55 ]
p obj1.an_integer # 1
p obj2.an_array # [ 55]
p obj2.an_integer # 55


We could write complex cloning methods that make sure all the objects held within are themselves clone, but there is an easier way. This is to use the Marshal module to serailize and immediately load the data into a new object. This only works if the object allows serialization, meaning it has a defined __dump and __load (true for most objects, but dont try this with the Window or Proc class).

CODE
obj2 = Marshal.load(Marshal.dump(obj1))


We are serializing obj1 into a string and immediately reconstituting the result into obj2. All objects held by instance variables within obj1 are considered new objects when loaded into obj2. This means when we alter the array in obj2, it is a now different array than the one held by obj1.an_array:

Spoiler:
CODE
class AnObj
  attr_accessor :an_array
  attr_accessor :an_integer
  def initialize(n, m)
    @an_array = [n]
    @an_integer = m
  end
end

obj1 = AnObj.new(1, 1)
obj2 = Marshal.load(Marshal.dump(obj1)) # deep clone
obj2.an_array[0] = 55 # element assignment was called on the array object held by an_array. However, this is a different array, so element assignement will not alter the array in obj1
obj2.an_integer = 55

p obj1.an_array # [ 1 ]
p obj1.an_integer # 1
p obj2.an_array # [ 55]
p obj2.an_integer # 55


The examples may be a bit contrived, but it is possible to have arrays nested in hashes further nested in arrays and you want to have a working copy of all the data.

Another useful application is to deep clone an actor object. Actors carry around all sorts of objects that you would want to use new objects for during a clone. For example (these assume you are working within the script editor, with a minimum of the default scripts):

Spoiler:
CODE
actor1 # this is a Game_Actor object
actor1.hp = 9999 # actor1 has full hp
test_actor = actor1.clone
test_actor.add_state(1) # add incapactiate state
p test_actor.state?(1) # true <= tests if state 1 is added, in this case, it was
p actor1.state?(1) # true <= actor1 has incapcitate state because the array holding states was not cloned
p test_actor.hp # 0 <= test actor has 0 hp because hp is set to 0 when incapaciate is assigned
p actor1.hp # 9999 <= inconsistent state, actor1 has incapacitate but remains 9999 hp, bugged


versus
CODE
actor1 # this is a Game_Actor object
actor1.hp = 9999 # actor1 has full hp
test_actor = Marshal.load(Marshal.dump(actor1)) # deep clone actor1
test_actor.add_state(1) # add incapactiate state
p test_actor.state?(1)  # true
p actor1.state?(1)  # false <= test_actor has a different state array, so incapacitate was not added
p test_actor.hp # 0 <= test_actor has 0 hp because hp is set to 0 when incapaciate is assigned
p actor1.hp # 9999 <= actor1 also remains 9999 hp



--------------------
Go to the top of the page
 
+Quote Post
   
BigEd781
post Oct 1 2009, 09:36 PM
Post #4


No method: 'stupid_title' found for 'nil:NilClass`
Group Icon


Type: Coder
Alignment: Chaotic Good




CODE
x = %w(this is a test)
x *= 2
=>["this", "is", "a", "test", "this", "is", "a", "test"]


When you use the '*' operator on an array (Array#*) with a numerical value, it multiplies the size (and contents) of the array, i.e., the array is duplicated n times. However, look at this code:

CODE
x = %w(this is a test)
x *= ','
=>"this,is,a,test"


Instead of using an integral value, we have passed a string to the Array#* method. When the input is a string, a join is automatically performed on the array!


--------------------
My blog - It's awesome, I assure you
QUOTE
While sloppy writing does not invariably mean sloppy thinking, we've generally found the correlation to be strong -- and we have no use for sloppy thinkers. If you can't yet write competently, learn to. - Eric Raymond

---
My awards for being so awesome
Spoiler:
Go to the top of the page
 
+Quote Post
   
BigEd781
post Oct 1 2009, 09:47 PM
Post #5


No method: 'stupid_title' found for 'nil:NilClass`
Group Icon


Type: Coder
Alignment: Chaotic Good




This one is very handy for formatting output, the String#% method:

CODE
hp = 123
formatted_hp = "%.4d" % hp
=>"0123"

money = 10.5
formatted_money = "$%.2f" % money
=>"$10.50"

scores = [1,2,3]
formatted_scores = "Billy: %d, Susan: %d, Janet: %d" % scores
=>"Billy: 1, Susan: 2, Janet: 3"


The first string is a format specifier, and the second argument is an array of values to replace the format specifiers in the first string. This is really just a shortcut for Kernel#sprintf (all format specifiers can be found by following that link).


--------------------
My blog - It's awesome, I assure you
QUOTE
While sloppy writing does not invariably mean sloppy thinking, we've generally found the correlation to be strong -- and we have no use for sloppy thinkers. If you can't yet write competently, learn to. - Eric Raymond

---
My awards for being so awesome
Spoiler:
Go to the top of the page
 
+Quote Post
   
BigEd781
post Oct 1 2009, 09:56 PM
Post #6


No method: 'stupid_title' found for 'nil:NilClass`
Group Icon


Type: Coder
Alignment: Chaotic Good




And another neato Ruby feature, the splat operator

This operator is used to "explode" enumerables. look at this code:

CODE
a, b, c = *[1, 2, 3]

a
=> 1
b
=> 2
a
=> 3


This can also be used in reverse:

CODE
*x = 1, 2, 3
x
=> [1,2,3]


So, when you see this construct:

CODE
class Foo
  
  alias :old_bar :bar
  def bar(*args)
    old_bar(*args)
    # do new stuff here
  end

end


Now you know what is going on. We just 'slurp' the arguments passed to Foo#bar into an enumerable collection, and then 'explode' them again when we call the aliased method. This allows us to alias the bar method without specifying an argument list (since we are not using them anyhow). Now, if someone comes along and changes the method signature of Foo#bar behind our backs our code still works.


--------------------
My blog - It's awesome, I assure you
QUOTE
While sloppy writing does not invariably mean sloppy thinking, we've generally found the correlation to be strong -- and we have no use for sloppy thinkers. If you can't yet write competently, learn to. - Eric Raymond

---
My awards for being so awesome
Spoiler:
Go to the top of the page
 
+Quote Post
   
BigEd781
post Oct 1 2009, 11:54 PM
Post #7


No method: 'stupid_title' found for 'nil:NilClass`
Group Icon


Type: Coder
Alignment: Chaotic Good




Almost forgot one of my favorites. Instead of writing code like this:

CODE
def some_method
   @foo = 0 if @foo.nil?
end


We can just write this:

CODE
def some_method
  @foo ||= 0
end


If @foo is nil (or false), we assign 0 to it, otherwise leave it as is. This is a little sneaky. Most people will assume that this is going on behind the covers (myself included in the beginning):

CODE
@foo = @foo || 0


But that is not the case. The true expansion is:

CODE
@foo || @foo = 0


That works because, if @foo evaluates to true (i.e., has a value other than nil or false) the right side will not be evaluated and @foo will be returns. However, if @foo evaluates to false (remember; nil evaluates to false) then the right hand side is evaluated, 0 is assigned to @foo, and the result of the expression (0) is returned.

Also this can help to cut down on lines when declaring local variables (just saw this usage for the first time earlier today):

CODE
(x ||= []) << 'test'


So, instead of defining a local variable with some initial content (often just an empty hash or array), you can instead define it "on the go" so you can perform operations on it at the same time. This would replace this construct:

CODE
x = []
x << 'test'


--------------------
My blog - It's awesome, I assure you
QUOTE
While sloppy writing does not invariably mean sloppy thinking, we've generally found the correlation to be strong -- and we have no use for sloppy thinkers. If you can't yet write competently, learn to. - Eric Raymond

---
My awards for being so awesome
Spoiler:
Go to the top of the page
 
+Quote Post
   
Mithran
post Oct 4 2009, 12:49 PM
Post #8


Scripter
Group Icon


Type: Coder
Alignment: True Neutral




While not widely used among RGSS2 scripts, it is entirely possible to define methods within other methods. Here, we have a class method define an instance method:

Spoiler:
CODE
class MyClass
  def self.add_method1
    def method1; end
  end
end

p MyClass.instance_methods(false) # []
MyClass.add_method1
p MyClass.instance_methods(false) # ["method1"] <= method1 was added


A method can even alias and redefine itself:

Spoiler:
CODE
def do_something
  print "before"
  alias do_something_before do_something unless defined?(do_something_before)
  def do_something
    print "after"
    def do_something
      do_something_before
    end
  end
end

do_something # => before
do_something # => after
do_something # => before
do_something # => after
do_something # => before
do_something # => after
do_something # => before
do_something # => after


This example defines do_something to print "before", then aliases itself as do_something_before and redefines itself to print "after", followed by redefining itself to do what it did in the first place (execpt to skip the alias, because the method is now defined when this line is reached and the line is qualified with an unless defined?).

Or with a Singleton:

Spoiler:
CODE
class MyClass
  def my_method
     print "original"
  end
  def mod_method
    def self.my_method # the value of 'self' at this point will be the instance of the class
      print "modified"
    end
  end
end

a = MyClass.new
b = MyClass.new

a.my_method # original
b.my_method # original
a.mod_method
a.my_method # modified
b.my_method # original
MyClass.new.my_method # original


Since the mod_method is an instance method, the value of 'self' at this point is the current instance object. Therefore, the encased method definition is for the current instance, or a Singleton method for the object.


--------------------
Go to the top of the page
 
+Quote Post
   
Prof. Meow Meow
post Dec 3 2009, 01:52 AM
Post #9



Group Icon


Type: Designer
Alignment: Neutral Good




I found multidimensional arrays to be cool and quite useful. I'm certain our programming verterans are familiar with them, but for everybody else, a multidimensional array is very much like a normal array, except instead of storing values as a list, you can store values as a grid.

In order to create a multidimensional array, you need a method to construct it. Here's the method:
CODE
def mda(row,column)
  Array.new(row).map!{ Array.new(column) }
end


When you declare your multidimensional array, you need to specify it's height and width.
To create a multidimensional array with a size of 10x10, declare it like this:
CODE
example_mda = mda(10,10)


Now to store and retrieve values:
CODE
example_mda[2][5] = "test string"
example_mda[3][3] = 7
print example_mda[2][5] # This will print "test string"
print example_mda[3][3] # This will print "7"




This post has been edited by Prof. Meow Meow: Dec 3 2009, 01:53 AM


--------------------
8 lives remaining.
Go to the top of the page
 
+Quote Post
   
FenixFyreX
post Jan 20 2012, 04:17 AM
Post #10


I'm on fire 24/7 >:3
Group Icon


Type: Coder
Alignment: Lawful Good




Hope this necropost is worthy enough..that said, I've been messing around with Hash#method_missing, and have come up with this:

CODE
class Hash
    alias method_missing_no_key method_missing
    def method_missing(method,*args,&block)
        sym = method.to_s.gsub("=","").to_sym
        if method.to_s[-1].chr == "="
            self[sym] = args[0]
        end
        return self[sym]
    end
end


Basically, when you call a method of an object and it is not found, #method_missing is called. The user can then modify this method to do what they wish, as long as they allow the default methods contents to work through as well. But, I wanted to call a hash's keys like methods, so I decided I'd utilize this function; simply catch the method being called, determine whether it is a setter or getter method, and operate on top of that.

So, this will not error when used in conjunction with the above code:
CODE
hash = {}
puts hash #=> {}

hash.owo = "owo"
puts hash.owo #=> "owo"
puts hash.owo == hash[:owo] #=> true
puts hash #=> {:owo => "owo"}
Go to the top of the page
 
+Quote Post
   

Reply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 

Lo-Fi Version Time is now: 17th April 2014 - 03:41 AM

RPGMakerVX.net is an Privacy Policy, Legal.