[Solution]: Lisp partial solution - meta-programming help

Discussion in 'Ruby' started by Louis J Scoras, Oct 3, 2005.

  1. ------=_Part_17982_9256152.1128372048201
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    Hi all;

    I started to give the lisp quiz a go. From the begining, I resolved to try
    for a solution such that the map can be set up in a declarative manner.

    I wanted it to look something like the attr_ class methods and ActiveRecord=
    s
    `associations`; however, setting up something like this seems to be more
    work than I thought it would be. Following is some working code, but it's
    pretty ugly. Setting the class variable in this manner just feels very
    hackish, yet it's the only way I could seem to get this to work.

    The problem was this: how to write the methods so that they don't have to b=
    e
    explicitly overwritten in the subclasses, and yet they can reference these
    concrete classes state?

    Anyone with a bit more ruby experiance have any refactoring ideas?


    --
    Lou


    module LocationClassMethods
    def exit_to(place,direction,opts=3Dnil)
    portal =3D (opts[:through] || opts[:via] if opts.respond_to?:[]) || 'door'
    class_variable_set:)@@exits, []) unless class_variables.include?:)@@
    exits.to_s)
    exits =3D class_variable_get:)@@exits)
    exits << Exit.new(direction,portal)
    end

    def description(desc)
    class_variable_set:)@@description, desc)
    end

    def exits
    class_variable_get:)@@exits)
    end

    def get_description
    class_variable_get:)@@description)
    end

    end

    class Location
    def self.inherited(sub)
    sub.extend(LocationClassMethods)
    end

    def initialize
    @exits =3D self.class.exits
    @description =3D self.class.get_description
    end

    def describe
    puts @description
    describe_exits
    end

    def describe_exits
    @exits.each { |e| puts e.describe }
    end
    end

    class Attic < Location
    exit_to :living_room, :down, :via =3D> 'staircase'

    description 'You are in the attic of the wizard\'s house. There is a giant
    welding torch in the corner.'
    end

    class Garden < Location
    exit_to :living_room, :west

    description 'You are standing in a beautiful garden. There is a well in
    front of you.'
    end

    class LivingRoom < Location
    exit_to :attic, :up, :via =3D> 'staircase'
    exit_to :garden, :east

    description 'You are in the living-room of a wizard\'s house. There is a
    wizard snoring loudly on the couch.'
    end


    a =3D Attic.new
    a.describe

    ------=_Part_17982_9256152.1128372048201--
     
    Louis J Scoras, Oct 3, 2005
    #1
    1. Advertising

  2. --liOOAslEiF7prFVr
    Content-Type: text/plain; charset=us-ascii
    Content-Disposition: inline

    On Tue, Oct 04, 2005 at 05:40:51AM +0900, Louis J Scoras wrote:
    > Anyone with a bit more ruby experiance have any refactoring ideas?


    Here's a sketch. It doesn't have the full feature set (portals, etc),
    but that should be easy to add. Instead of messing around with class
    variables, we simply define methods that return the values we want.
    The data is stored implicitly within blocks.

    class Location
    def self.description(d)
    # Even though we're using a constant method name, we use
    # "define_method" instead of "def" in order to form a closure over
    # the local variable "d".
    define_method:)describe) do
    d
    end
    end

    def self.exit_to(direction,location)
    # @exits is a member of the class object itself, not the instances
    @exits ||= {}
    @exits[direction] = location
    e = @exits
    define_method:)exits) do
    e
    end
    end
    end

    class A < Location
    exit_to :north, :b
    exit_to :east, :garden
    description "You're at point A. It's very boring."
    end

    class B < Location
    exit_to :south, :a
    description "Point B is even more boring than Point A."
    end

    regards,
    Ed

    --liOOAslEiF7prFVr
    Content-Type: application/pgp-signature; name="signature.asc"
    Content-Description: Digital signature
    Content-Disposition: inline

    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.1 (GNU/Linux)

    iD8DBQFDQbSRnhUz11p9MSARAu6uAJ93tR4X5KKjXKA9TXIN0opHwozouQCgmYsu
    PL0EQ3KtRkoxwOfTVkZZ6Fk=
    =TOsh
    -----END PGP SIGNATURE-----

    --liOOAslEiF7prFVr--
     
    Edward Faulkner, Oct 3, 2005
    #2
    1. Advertising

  3. Along the same lines, as I am unlikely to pursue this any further,
    here is my half-baked doodle towards a game DSL :)

    You can move about (using 'east', 'west', 'upstairs', etc.) but nothing els=
    e.

    Regards,
    Sean

    -- CODE --
    module Attributes
    def has(*names)
    self.class_eval {
    names.each do |name|
    define_method(name) {|*args|
    if args.size > 0
    instance_variable_set("@#{name}", *args)
    else
    instance_variable_get("@#{name}")
    end
    }
    end
    }
    end

    end

    module Directions
    def directions(*directions)
    directions.each do |name|
    self.class.class_eval {
    define_method(name) {
    dest =3D @location.exits[name]
    if dest
    @location =3D @rooms[dest[1]]
    look
    else
    puts "You can't move in that direction"
    end
    }
    }
    end
    end
    end

    class GameObject
    extend Attributes
    has :identifier, :name, :description
    def initialize(identifier, &block)
    @identifier =3D identifier
    instance_eval &block
    end
    end

    class Thing < GameObject
    has :location
    end

    class Room < GameObject
    has :exits

    def initialize(identifier, &block)
    # put defaults before super - they will be overridden in block (if at a=
    ll)
    super
    end

    end

    class Game
    include Directions

    attr_accessor :name, :rooms, :location, :things

    def initialize(name, &block)
    @name =3D name
    @rooms =3D {}
    @things =3D {}

    # read game definition
    instance_eval &block

    end

    def room(identifier, &block)
    @rooms[symbol(identifier)] =3D Room.new(symbol(identifier), &block)
    end

    def thing(identifier, &block)
    @things[symbol(identifier)] =3D Thing.new(symbol(identifier), &block)
    end

    def symbol(s)
    s.to_s.to_sym
    end

    def start(room_identifier)
    @location =3D @rooms[room_identifier]
    end

    def describe_path(direction, path)
    "There is a #{path} going #{direction}."
    end

    def describe_exits(location)
    location.exits.map {|direction, (path, destination)|
    describe_path(direction, path)
    }
    end

    def describe_floor(location)
    @things.select{|key, thing| thing.location =3D=3D
    location.identifier}.map{|key, thing| "There is a #{thing.description}
    here."}
    end

    def main_loop
    while input =3D gets
    input.chomp!
    case input
    when 'exit', 'quit'
    break
    when 'help'
    puts "Sorry pal! You're on your own here :)"
    else
    begin
    instance_eval input
    rescue Exception =3D> e
    puts e.to_s
    puts "Eh?"
    end
    end
    end
    end


    # commands

    def look
    puts location.description
    puts describe_exits(location)
    puts describe_floor(location)
    end

    def quit
    break
    end

    end

    def game(name, &block)
    g =3D Game.new(name, &block)
    g.look
    g.main_loop
    end

    # Game definition

    game "Ruby Adventure" do

    directions :east, :west, :north, :south, :up, :down, :upstairs, :downstai=
    rs

    room :living_room do
    name 'Living Room'
    description "You are in the living-room of a wizard's house. There
    is a wizard snoring loudly on the couch."
    exits :west =3D> [:door, :garden],
    :upstairs =3D> [:stairway, :attic]
    end

    room :garden do
    name 'Garden'
    description "You are in a beautiful garden. There is a well in
    front of you."
    exits :east =3D> [:door, :living_room]
    end

    room :attic do
    name "Attic"
    description "You are in the attic of the wizard's house. There is
    a giant welding torch in the corner."
    exits :downstairs =3D> [:stairway, :living_room]
    end

    thing :whiskey_bottle do
    name 'whiskey bottle'
    description 'half-empty whiskey bottle'
    location :living_room
    end

    thing :bucket do
    name 'bucket'
    description 'rusty bucket'
    location :living_room
    end

    thing :chain do
    name 'chain'
    description 'sturdy iron chain'
    location :garden
    end

    thing :frog do
    name 'frog'
    description 'green frog'
    location :garden
    end

    start :living_room

    end
    -- END --
     
    Sean O'Halpin, Oct 4, 2005
    #3
  4. ------=_Part_439_20500209.1128386563313
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    Okay cool. So closures are the way to go. That's the way I would have done
    it in perl, but I didn't know if all the OO magic was prefered way in Ruby.
    Plus, I was looking at the Pickaxe reference for the Module Class
    documentation =3D)

    That helps a lot, thanks.


    --
    Lou

    ------=_Part_439_20500209.1128386563313--
     
    Louis J Scoras, Oct 4, 2005
    #4
  5. Re: Lisp Game

    On Oct 3, 2005, at 6:01 PM, Mark J.Reed wrote:

    > I'm trying to come up with a way to have my cake and eat it, too.
    > Is there
    > any way to hook into irb and have some code executed every time the
    > prompt
    > is displayed, like $PROMPT_COMMAND in bash? That would let me work
    > around
    > this . . .


    This was quite problematic when I was building my own version. I
    tried to get around it with my $stringify global, but that requires
    you to spell out all allowed words.

    I'm very it interested in the answers to your irb questions, though,
    so we will both await the responses from the gurus...

    James Edward Gray II

    P.S. Please do share your code at some point... Looks great!
     
    James Edward Gray II, Oct 4, 2005
    #5
  6. Re: Lisp Game

    Here's one way to hook into irb's eval loop:

    module IRB
    class Context
    def evaluate(line, line_no)
    value =3D @workspace.evaluate(self, line, irb_path, line_no)
    puts "value =3D #{value}" # do something with returned value here
    value
    end
    end
    end

    Regards,

    Sean
     
    Sean O'Halpin, Oct 4, 2005
    #6
  7. Re: Lisp Game

    Oops - that should have been:

    module IRB
    class Context
    def evaluate(line, line_no)
    value =3D @workspace.evaluate(self, line, irb_path, line_no)
    puts "value =3D #{value}"
    set_last_value(value)
    end
    end
    end

    (Note set_last_value)

    Sean
     
    Sean O'Halpin, Oct 4, 2005
    #7
  8. Re: Lisp Game

    On Oct 3, 2005, at 8:11 PM, Mark J.Reed wrote:

    > $objects.find_all do
    > |obj|


    I had to double-check that to convince myself it works. I thought I
    remembered reading more than once that the block arguments had to be
    on the same line as the start of the block.

    Has that been changed, or is my memory fuzzy?

    James Edward Gray II
     
    James Edward Gray II, Oct 4, 2005
    #8
  9. Louis J Scoras

    Adam Shelly Guest

    Re: Lisp Game

    On 10/3/05, James Edward Gray II <> wrote:
    > This was quite problematic when I was building my own version. I
    > tried to get around it with my $stringify global, but that requires
    > you to spell out all allowed words.


    I solved it by letting method_missing fill in the allowed words itself.
    The trick is that all the allowed words are used in Game#initialize,
    so after the global Game object is constructed, any additional unknown
    words must be invalid commands.

    class Game
    def initialize
    @objects =3D [whiskey_bottle, frog, bucket, chain]
    #...
    end
    #...
    end

    $g =3D Game.new
    $valid_words =3D []

    def method_missing symbol, *args
    #if the game has not started, add the symbol to the list of valid to=
    kens
    if !$g
    $valid_words << symbol
    symbol.to_s
    #if the game has started, see if it is a token
    elsif $words.include? symbol
    symbol.to_s
    #otherwise, it is an unknown command.
    else
    puts "> #{symbol} #{args.join ' '}" if !$Interactive
    puts "Sorry, I don't understand #{symbol}"
    end
    end



    -Adam
     
    Adam Shelly, Oct 4, 2005
    #9
  10. Louis J Scoras

    Adam Shelly Guest

    Re: Lisp Game

    oops:
    > elsif $words.include? symbol

    should be:
    elsif $valid_words.include? symbol
     
    Adam Shelly, Oct 4, 2005
    #10
  11. ------=_Part_15709_8912280.1128458746832
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    So what would be the best way to do this if the data needs to be read-write=
    ?

    In the context of the game---and the code above---I guess we could try to
    create two methods (getter/setter) that close over the same shared variable=
     
    Louis J Scoras, Oct 4, 2005
    #11
  12. --45Z9DzgjV8m4Oswq
    Content-Type: text/plain; charset=us-ascii
    Content-Disposition: inline
    Content-Transfer-Encoding: quoted-printable

    On Wed, Oct 05, 2005 at 05:45:49AM +0900, Louis J Scoras wrote:
    > So what would be the best way to do this if the data needs to be read-wri=

    te?
    =2E..
    > But what would you do if the classes in question weren't meant to be
    > singletons?=20


    In that case the data must ultimately be stored in the individual
    instances. You can copy it there at #initialize time. Check out
    _why's Creature class at the bottom of this page:

    http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html

    > Trying to use module_eval to dynamically define a class method doesn't se=

    em
    > to work, because it's not available in the constructor.


    You can dynamically define class methods like this:

    class Metaprogrammable
    def self.add_feature(f)
    meta =3D class << self; self; end
    meta.instance_eval {
    define_method(f) do
    "This is class method #{f}"
    end
    }
    end
    end

    class Foo < Metaprogrammable
    add_feature :bogus
    end

    Remember that a Class is also an Object. When you create class
    methods, you're extending one particular instance of the class
    "Class". So this:

    class << MyClass
    def a_class_method
    ...
    end
    end

    is equivalent to this:

    def MyClass.a_class_method
    ...
    end

    and also this:

    class MyClass
    def self.a_class_method
    ...
    end
    end

    regards,
    Ed

    --45Z9DzgjV8m4Oswq
    Content-Type: application/pgp-signature; name="signature.asc"
    Content-Description: Digital signature
    Content-Disposition: inline

    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.1 (GNU/Linux)

    iD8DBQFDQvE7nhUz11p9MSARAkmfAKDCzRPhLJ2E7D7Qr1Cql7wBvg7EFQCgzxzt
    cENHN5h8qozkA+iVVQg2zV0=
    =wf31
    -----END PGP SIGNATURE-----

    --45Z9DzgjV8m4Oswq--
     
    Edward Faulkner, Oct 4, 2005
    #12
  13. Re: : Lisp partial solution - meta-programming help

    Maybe I read the original lesson wrong, but I understood the point of
    that article to be how to use Lisp macros (which he renames 'spel's so
    as not to confuse people). I'm new to Ruby, but it seems that Ruby
    doesn't offer such a mechanism. Most solutions here seem to use
    method_missing to handle the new features, but that really isn't the
    same as a macro.

    Does anyone know if macros will be introduced into Ruby in 1.9 or 2.0?
    Even something like Dylan's macros would be great.
    http://www.gwydiondylan.org/books/dpg/db_329.html
    The latest description of Ruby 2.0 I know of doesn't mention macros at
    all. http://www.rubygarden.org/ruby?Rite Do people here think macros
    are not useful?

    -mike
     
    Mike Salisbury, Oct 5, 2005
    #13
  14. Louis J Scoras

    Dave Burt Guest

    Re: : Lisp partial solution - meta-programming help

    Mike Salisbury...
    > Maybe I read the original lesson wrong, but I understood the point of
    > that article to be how to use Lisp macros (which he renames 'spel's so
    > as not to confuse people).


    I don't think so; it looks to me like a general intro, with macros included
    to get a feeling for what LISP can do.

    > I'm new to Ruby, but it seems that Ruby
    > doesn't offer such a mechanism. Most solutions here seem to use
    > method_missing to handle the new features, but that really isn't the
    > same as a macro.


    That's right. eval is as close as Ruby gets to macros, although ParseTree
    may give similar capability down the track (now even?).

    > Does anyone know if macros will be introduced into Ruby in 1.9 or 2.0?
    > Even something like Dylan's macros would be great.
    > http://www.gwydiondylan.org/books/dpg/db_329.html
    > The latest description of Ruby 2.0 I know of doesn't mention macros at
    > all. http://www.rubygarden.org/ruby?Rite Do people here think macros
    > are not useful?


    I daresay macros aren't a part of the "Ruby Way," and I'm pretty sure you
    won't see them in Ruby 2.0. This doesn't mean they can't be implemented as a
    library.

    Cheers,
    Dave
     
    Dave Burt, Oct 5, 2005
    #14
    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. ekzept
    Replies:
    0
    Views:
    374
    ekzept
    Aug 10, 2007
  2. nanothermite911fbibustards
    Replies:
    0
    Views:
    379
    nanothermite911fbibustards
    Jun 16, 2010
  3. nanothermite911fbibustards
    Replies:
    0
    Views:
    319
    nanothermite911fbibustards
    Jun 16, 2010
  4. Erik Veenstra

    Meta-Meta-Programming

    Erik Veenstra, Feb 7, 2006, in forum: Ruby
    Replies:
    29
    Views:
    390
    Erik Veenstra
    Feb 8, 2006
  5. Erik Veenstra

    Meta-Meta-Programming, revisited

    Erik Veenstra, Jul 21, 2006, in forum: Ruby
    Replies:
    21
    Views:
    442
    Erik Veenstra
    Jul 25, 2006
Loading...

Share This Page