Functional programming in Ruby

B

Brian Candler

Not a question or anything... I just wanted to share this snippet with
any non-computer-scientist who thinks this is cool :)

I come from very much an imperative programming background - originally
machine code. Computer science books tend to use LISP, and I find anything
other than the simplest example to be impenetrable. However, translating
them to Ruby makes it much clearer to me what's going on.

---------------------------------------------------------------------------
# Simple start: implement the 'times' iterator recursively, applied
# to an explicit proc argument rather than an implicit block.

def my_times(n, f)
if n >= 1
f.call()
my_times(n-1, f)
end
end

my_times(3, proc { puts "testing" } )

# OK, now implement the 'times' iterator as an anonymous function (proc)

times = proc { |n, f|
if n >= 1
f.call()
times.call(n-1, f)
end
}

times.call(3, proc { puts "hello world" } )

# However, I cheated :) The proc isn't really anonymous because I assigned
# it to 'times', and this was essential because I referred to the name
# inside the function in order to call itself recursively.
#
# But in fact it's possible to write fully anonymous functions which are
# recursive.
#
# The following example is translated from "Structure and Interpretation
# of Computer Programs" (Abelson, Sussman and Sussman), second edition p393
# - it's an anonymous function which calculates factorial recursively

puts proc { |n|
proc { |fact| fact.call(fact, n) }.call(
proc { |ft, k|
k <= 1 ? 1 : k * ft.call(ft, k-1)
}
)
}.call(10)

# Using this pattern we can recast our iterator as follows, without using
# its name internally:

proc { |*a|
proc { |iter| iter.call(iter, *a) }.call(
proc { |me, n, f|
if n >= 1
f.call()
me.call(me, n-1, f)
end
}
)
}.call(3, proc { puts "hello again" } )
 
C

Chad Perrin

Not a question or anything... I just wanted to share this snippet with
any non-computer-scientist who thinks this is cool :)

I come from very much an imperative programming background - originally
machine code. Computer science books tend to use LISP, and I find anything
other than the simplest example to be impenetrable. However, translating
them to Ruby makes it much clearer to me what's going on.

If you want to play with a more "functional" language than Ruby, you
might try ML (or OCaml), Haskell, or UCBLogo. The first of these is
pretty accessible to someone coming from an imperative and OOP
background because it is not *just* a functional language -- it also
provides integral OOP and imperative constructs. The second can be
pretty impenetrable to someone not already familiar with functional
programming, but it is about as "pure" an FP language as you're likely
to find. UCBLogo is like readable Lisp (complete with macros), and
there's a trilogy of college programming and CompSci textbooks available
for free online for it. Roughly open source implementations of all
three languages are available (I say "roughly" because the OCaml license
only allows you to distribute alterations to the "official" codebase via
patches).

Of course, Ruby's good for learning functional programming concepts, up
to a point, too. I, for one, am using Ruby more for enhancing my OOP
skills. I'll be using the other three languages I mentioned for my
further FP investigations, I'm sure. Of the two, I've already started
playing with OCaml and UCBLogo over the course of the last year, and
found a lot to like about both.
 
J

James Edward Gray II

puts proc { |n|
proc { |fact| fact.call(fact, n) }.call(
proc { |ft, k|
k <= 1 ? 1 : k * ft.call(ft, k-1)
}
)
}.call(10)
proc { |*a|
proc { |iter| iter.call(iter, *a) }.call(
proc { |me, n, f|
if n >= 1
f.call()
me.call(me, n-1, f)
end
}
)
}.call(3, proc { puts "hello again" } )

Wow, those melted my brain.

I kept thinking I could peel off the outer layer off the factorial
one, but I didn't succeed. Wild stuff.

Thanks for sharing.

James Edward Gray II
 
B

Brian Candler

Wow, those melted my brain.

I kept thinking I could peel off the outer layer off the factorial
one, but I didn't succeed. Wild stuff.

Here's perhaps a cleaner version:

proc { |n1, f1|
proc { |func, *args| func.call(func, *args) }.call(
proc { |me, n, f|
if n >= 1
f.call()
me.call(me, n-1, f)
end
}, n1, f1
)
}.call(3, proc { puts "hello again" } )

Line 2 encapsulates "a function which just calls the function+args you pass
in, except passing the function itself as an extra argument"

Cheers,

Brian.
 
R

Raj Sahae

I'm going to try to explain this problem without posting huge amounts of
code, so please stick with me.

Imagine you have an object, GameServer, that contains an instance of
some object, Game.
Game has many objects it owns too, Player's, Deck's, Card's, and all
these objects have methods.
Then, I start a DRb service, passing in GameServer.game. This isn't
what the code actually is, but a
basic skeleton would be something like:

Class GameServer
def initialize
@game = Game.new
end
end

Class Game
def initialize
@players = Array.new #holds Player.new instances
end
end

Class Player
def initialize
@name
@hand = Array.new # holds Cards
@deck = Array.new # holds Cards
end

def draw
@hand << @deck.shift
end
end

DRb.start service etc etc


I have a client that starts a DRb service, creating a DRbObject game. I
wasn't successfull in using DRbUndumped, so I have all the classes
defined on both the server and the client(probably related to the
problem but I can't get DRbUndumped to work). I know the connection
works because I have accessed data through the connection, but at some
point, the client calls a method on the DRbObject, and nothing happens.
It's a Player.draw method, that takes a Card from Deck, and puts it in
Player.hand. I threw some prints in there, so I know the method is
being called, but it's not having the desired effect. I want the object
on the server to change, but apparently the intuitive way to go about
that isn't the correct way. When I call the method, the prints show up
in the clients prompt. How do I activate the method on the server, from
the client, thereby manipulating the object on the server?

I know that paragraph can be quite confusing. Please ask for
clarification where needed.

Raj Sahae
 
E

Eric Hodel

To start off, don't hijack threads by changing the subject. Start
new threads.

In other words, use the "Reply" button to create a new thread. That
is what the "New" button is for.

I'm going to try to explain this problem without posting huge
amounts of code, so please stick with me.

Imagine you have an object, GameServer, that contains an instance
of some object, Game.
Game has many objects it owns too, Player's, Deck's, Card's, and
all these objects have methods.
Then, I start a DRb service, passing in GameServer.game. This
isn't what the code actually is, but a
basic skeleton would be something like:

Class GameServer

include DRbUndumped
def initialize
@game = Game.new
end
end

Class Game

include DRbUndumped
def initialize
@players = Array.new #holds Player.new instances
end
end

Class Player

include DRbUndumped
def initialize
@name
@hand = Array.new # holds Cards
@deck = Array.new # holds Cards
end

def draw
@hand << @deck.shift
end
end

DRb.start service etc etc


I have a client that starts a DRb service, creating a DRbObject
game. I wasn't successfull in using DRbUndumped, so I have all the
classes defined on both the server and the client(probably related
to the problem but I can't get DRbUndumped to work).

To write a game server with multiple clients you're going to need to
use DRbUndumped. Without it each client has their own deck, so one
client drawing a card won't affect any other client's decks.
I know the connection works because I have accessed data through
the connection, but at some point, the client calls a method on the
DRbObject, and nothing happens.

Because each client has its own copy of the game.
It's a Player.draw method, that takes a Card from Deck, and puts it
in Player.hand. I threw some prints in there, so I know the method
is being called, but it's not having the desired effect. I want
the object on the server to change, but apparently the intuitive
way to go about that isn't the correct way.

Adding DRbUndumped to your classes will fix this.
When I call the method, the prints show up in the clients prompt.
How do I activate the method on the server, from the client,
thereby manipulating the object on the server?

Use DRbUndumped.
I know that paragraph can be quite confusing. Please ask for
clarification where needed.

DRbUndumped forces RMI.

Without DRbUndumped each client receives a copy of the object on the
server. Your client is sending messages to the client's object
instead of sending messages to the server's object. With DRbUndumped
there exists the copy on the server and a proxy object on the client
which forwards messages to the server.

Note that DRb is not really client-server, but peer-to-peer, as any
client may also be a server.
 
B

Brian Candler

DRbUndumped forces RMI.

Without DRbUndumped each client receives a copy of the object on the
server. Your client is sending messages to the client's object
instead of sending messages to the server's object. With DRbUndumped
there exists the copy on the server and a proxy object on the client
which forwards messages to the server.

Note that DRb is not really client-server, but peer-to-peer, as any
client may also be a server.

Also, google for "drbtutorial". This points to a Rubygarden Wiki page.
Unfortunately, Rubygarden appears to be out of service, and the Google cache
isn't returning the page either, but you can get to it via the Wayback
Machine at archive.org:

http://web.archive.org/web/20060430030849re_/www.rubygarden.org/ruby?DrbTutorial

The section headed "Why does the client run 'DRb.start_service'?" explains a
bit more about DRbUndumped and the peer-to-peer behaviour of DRb.

Regards,

Brian.
 
J

James Edward Gray II

In other words, use the "Reply" button to create a new thread.
That is what the "New" button is for.

There is a word missing in the first sentence about that reverses its
meaning. Eric meant to say:

In other words, *don't* use the "Reply"...

James Edward Gray II
 
R

Raj Sahae

James said:
There is a word missing in the first sentence about that reverses its
meaning. Eric meant to say:

In other words, *don't* use the "Reply"...
Yeah, I figured that out. Sorry about hijacking the thread. I wrote
the post by email, I didn't go to the forum. For some reason, I assumed
that if I changed the subject and sent it to the talk-list, it would
make a new post. What method does it use to detect if an email is a
reply or a new post?

Raj
 
J

James Edward Gray II

Yeah, I figured that out. Sorry about hijacking the thread. I
wrote the post by email, I didn't go to the forum. For some
reason, I assumed that if I changed the subject and sent it to the
talk-list, it would make a new post. What method does it use to
detect if an email is a reply or a new post?

Most mail clients use headers in the email message. I believe the
one that applies here is In-reply-to.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top