Functional programming in Ruby

Discussion in 'Ruby' started by Brian Candler, Feb 28, 2007.

  1. 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" } )
    Brian Candler, Feb 28, 2007
    #1
    1. Advertising

  2. Brian Candler

    Chad Perrin Guest

    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
    Chad Perrin, Mar 1, 2007
    #2
    1. Advertising

  3. 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
    James Edward Gray II, Mar 1, 2007
    #3
  4. 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.
    Brian Candler, Mar 1, 2007
    #4
  5. Brian Candler

    Raj Sahae Guest

    problem with DRb

    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
    Raj Sahae, Mar 1, 2007
    #5
  6. Brian Candler

    Eric Hodel Guest

    Re: problem with DRb

    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.
    Eric Hodel, Mar 1, 2007
    #6
  7. Re: problem with DRb

    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/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.
    Brian Candler, Mar 1, 2007
    #7
  8. Re: problem with DRb

    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
    James Edward Gray II, Mar 1, 2007
    #8
  9. Brian Candler

    Raj Sahae Guest

    Re: problem with DRb

    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
    Raj Sahae, Mar 1, 2007
    #9
  10. Re: problem with DRb

    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
    James Edward Gray II, Mar 1, 2007
    #10
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Casey Hawthorne
    Replies:
    4
    Views:
    996
    Jarek Zgoda
    Aug 4, 2006
  2. Joe Mayo
    Replies:
    168
    Views:
    3,293
    David Thompson
    Oct 22, 2007
  3. Jesse Merriman

    Backus, Functional Programming, and Ruby

    Jesse Merriman, Mar 25, 2007, in forum: Ruby
    Replies:
    10
    Views:
    227
    Giles Bowkett
    Mar 26, 2007
  4. Philip Rhoades
    Replies:
    4
    Views:
    100
    James Britt
    Jun 29, 2008
  5. Yu-Hsuan Lai
    Replies:
    1
    Views:
    97
Loading...

Share This Page