Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Functional programming in Ruby

Reply
Thread Tools

Functional programming in Ruby

 
 
Brian Candler
Guest
Posts: n/a
 
      02-28-2007
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" } )


 
Reply With Quote
 
 
 
 
Chad Perrin
Guest
Posts: n/a
 
      03-01-2007
On Thu, Mar 01, 2007 at 07:20:28AM +0900, Brian Candler wrote:
> 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.

--
CCD CopyWrite Chad Perrin [ http://ccd.apotheon.org ]
"The ability to quote is a serviceable
substitute for wit." - W. Somerset Maugham

 
Reply With Quote
 
 
 
 
James Edward Gray II
Guest
Posts: n/a
 
      03-01-2007
On Feb 28, 2007, at 4:20 PM, Brian Candler wrote:

> 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


 
Reply With Quote
 
Brian Candler
Guest
Posts: n/a
 
      03-01-2007
On Thu, Mar 01, 2007 at 10:35:43AM +0900, James Edward Gray II wrote:
> >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.


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.

 
Reply With Quote
 
Raj Sahae
Guest
Posts: n/a
 
      03-01-2007
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

 
Reply With Quote
 
Eric Hodel
Guest
Posts: n/a
 
      03-01-2007
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.

On Feb 28, 2007, at 23:42, Raj Sahae wrote:

> 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.

 
Reply With Quote
 
Brian Candler
Guest
Posts: n/a
 
      03-01-2007
On Thu, Mar 01, 2007 at 05:25:58PM +0900, Eric Hodel wrote:
> 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/200604300...by?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.

 
Reply With Quote
 
James Edward Gray II
Guest
Posts: n/a
 
      03-01-2007
On Mar 1, 2007, at 2:25 AM, Eric Hodel wrote:

> 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

 
Reply With Quote
 
Raj Sahae
Guest
Posts: n/a
 
      03-01-2007
James Edward Gray II wrote:
> On Mar 1, 2007, at 2:25 AM, Eric Hodel wrote:
>
>> 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"...

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


 
Reply With Quote
 
James Edward Gray II
Guest
Posts: n/a
 
      03-01-2007
On Mar 1, 2007, at 1:44 PM, Raj Sahae wrote:

> James Edward Gray II wrote:
>> On Mar 1, 2007, at 2:25 AM, Eric Hodel wrote:
>>
>>> 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"...

> 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

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Should I do functional programming in Ruby? Yu-Hsuan Lai Ruby 1 11-25-2010 09:55 PM
Lambda calculus & functional programming - the view from Ruby Philip Rhoades Ruby 4 06-29-2008 05:22 AM
C (functional programming) VS C++ (object oriented programming) Joe Mayo C Programming 168 10-22-2007 01:00 AM
Backus, Functional Programming, and Ruby Jesse Merriman Ruby 10 03-26-2007 06:42 PM
Can Your Programming Language Do This? Joel on functional programming and briefly on anonymous functions! Casey Hawthorne Python 4 08-04-2006 05:23 AM



Advertisments