Forum: Ruby Functional programming in Ruby

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Brian C. (Guest)
on 2007-03-01 00:21
(Received via mailing list)
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" } )
Chad P. (Guest)
on 2007-03-01 02:04
(Received via mailing list)
On Thu, Mar 01, 2007 at 07:20:28AM +0900, Brian C. 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.
James G. (Guest)
on 2007-03-01 03:35
(Received via mailing list)
On Feb 28, 2007, at 4:20 PM, Brian C. 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 G. II
Brian C. (Guest)
on 2007-03-01 08:36
(Received via mailing list)
On Thu, Mar 01, 2007 at 10:35:43AM +0900, James Edward G. II wrote:
>
> 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.
Raj S. (Guest)
on 2007-03-01 09:43
(Received via mailing list)
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 S.
Eric H. (Guest)
on 2007-03-01 10:26
(Received via mailing list)
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 S. wrote:

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

>
> 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.
Brian C. (Guest)
on 2007-03-01 11:26
(Received via mailing list)
On Thu, Mar 01, 2007 at 05:25:58PM +0900, Eric H. 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/20060430030849re_/www.r...

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.
James G. (Guest)
on 2007-03-01 15:04
(Received via mailing list)
On Mar 1, 2007, at 2:25 AM, Eric H. 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 G. II
Raj S. (Guest)
on 2007-03-01 21:45
(Received via mailing list)
James Edward G. II wrote:
> On Mar 1, 2007, at 2:25 AM, Eric H. 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
James G. (Guest)
on 2007-03-01 21:47
(Received via mailing list)
On Mar 1, 2007, at 1:44 PM, Raj S. wrote:

> 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 G. II
This topic is locked and can not be replied to.