Functional programming in Ruby

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

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 :slight_smile: 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" } )

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.

路路路

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 :slight_smile:

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.

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

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

路路路

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" } )

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.

路路路

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.

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

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.

路路路

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

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.

路路路

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.

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

路路路

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.

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

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

James Edward Gray II

路路路

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?