using a module at the toplevel doesn't work

T

Trans

Why? Becuase the module uses #define_method, and #define_method isn't
defined in main.

Which brings me to two questions:

1. How do I get around this problem? Please save me from having to
delgate the whole module!

2. Why isn't main a self extended module to begin with? Honestly, this
question has been buigging me for a long time.

T.
 
L

Logan Capaldo

Why? Becuase the module uses #define_method, and #define_method isn't
defined in main.

Which brings me to two questions:

1. How do I get around this problem? Please save me from having to
delgate the whole module!
irb(main):006:0> case self
irb(main):007:1> when Module, Class
irb(main):008:1> define_method:)a) { 1 }
irb(main):009:1> else
irb(main):010:1* (class << self; self; end).class_eval
{ define_method:)a) { 1 } }
irb(main):011:1> end

2. Why isn't main a self extended module to begin with? Honestly, this
question has been buigging me for a long time.
Couldn't tell you.
 
T

Trans

Logan said:
irb(main):006:0> case self
irb(main):007:1> when Module, Class
irb(main):008:1> define_method:)a) { 1 }
irb(main):009:1> else
irb(main):010:1* (class << self; self; end).class_eval
{ define_method:)a) { 1 } }
irb(main):011:1> end

That's what I want to avoid -- "delegating the whole module" :-(
Couldn't tell you.

Matz?

T.
 
T

Trans

Trans said:
That's what I want to avoid -- "delegating the whole module" :-(

Craggy. It's even worse than that! If I delegate via the singelton of
main then:

class << self
def x; "x"; end
end

class C
def q; x; end
end

C.new.q
=> NameError: undefined local variable or method `x' for
#<C:0xb7cfffc4>

(*frustrated*) It's not the same as defining at the top level.

T.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: using a module at the toplevel doesn't work"

|Why? Becuase the module uses #define_method, and #define_method isn't
|defined in main.
|
|Which brings me to two questions:
|
|1. How do I get around this problem? Please save me from having to
|delgate the whole module!
|
|2. Why isn't main a self extended module to begin with? Honestly, this
|question has been buigging me for a long time.

I don't think I understand you. Can you elaborate?

matz.
 
P

Pit Capitain

Trans said:
Craggy. It's even worse than that! If I delegate via the singelton of
main then:

class << self
def x; "x"; end
end

class C
def q; x; end
end

C.new.q
=> NameError: undefined local variable or method `x' for
#<C:0xb7cfffc4>

(*frustrated*) It's not the same as defining at the top level.

Tom, defining a method at the top level is the same as defining a
private method of Object:

def m
end

is the same as

class Object
private
def m
end
end

What are you trying to do?

Regards,
Pit
 
T

Trans

Yukihiro said:
I don't think I understand you. Can you elaborate?

Sure thing. It seems natural to me that "main" would a module of the
form:

module Main
extend self
end

Such a module provides all the characteristics of the toplevel --def,
include, etc.

Hmmm.... now that I spell it out.... Given that every method defined in
main becomes a private method of Object, this Main object looks a whole
lot like Kernel itself. So maybe that's a better way to think about it:
Why isn;t the toplevel Kernel (w/toplevel methods being private methods
of Kernel)?

T.
 
T

Trans

Hi Capitain,
What are you trying to do?

Well, I have module called Taskable. It's an emulation of Rake's basic
task pattern. eg.

desc "foo description"

task :foo => [ :foo_prerequisite ] do
...
end

But rather than define tasks globally as with Rake's system. I designed
it to work within modules/classes namspace including the tasks being
actual methods. So the one can do:

class X
task :a => [:b] do
print "a"
end

task :b do
print "b"
end
end

X.new.a; puts
=> ba

Unfortuately when I use this to make a Rake command-line emulator I
want to use the module at the toplevel and can't.

Thanks,
T.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: using a module at the toplevel doesn't work"

|Yukihiro Matsumoto wrote:
|
|> I don't think I understand you. Can you elaborate?
|
|Sure thing. It seems natural to me that "main" would a module of the
|form:
|
| module Main
| extend self
| end
|
|Such a module provides all the characteristics of the toplevel --def,
|include, etc.

Why? If it is really required it's fairly easy to add toplevel
methods like we did for #include.

matz.
 
T

Trans

Yukihiro said:
Hi,

In message "Re: using a module at the toplevel doesn't work"

|Yukihiro Matsumoto wrote:
|
|> I don't think I understand you. Can you elaborate?
|
|Sure thing. It seems natural to me that "main" would a module of the
|form:
|
| module Main
| extend self
| end
|
|Such a module provides all the characteristics of the toplevel --def,
|include, etc.

Why? If it is really required it's fairly easy to add toplevel
methods like we did for #include.

See what I wrote to Pit Capitain. Even if I define the missing toplevel
methods:

def define_method( meth, &block )
Object.class_eval do
define_method( meth, &block )
private meth
end
end

def ancestors
Object.ancestors
end

include MyModule

I still get errors because state (e.g. instance vars used in MyModule)
are being stored in the toplevel instance of Object, main, and not in
Object. I got methods going one way and instance vars going another.

Is there a reason the toplevel _can't_ be Kernel instead of a specal
instance of Object? Just seems like that would be hek of a lot easier
all around.

Thanks,
T.
 
P

Pit Capitain

Trans said:
Unfortuately when I use this to make a Rake command-line emulator I
want to use the module at the toplevel and can't.

Tom, I think I still don't get what your problem is. If you want to use
a module at the toplevel just include it in Object.

The module:

module M
def val *args
if args.empty?
@val
else
@val = args[ 0 ]
end
end
end

I used some instance variables here, because you wrote about them in the
answer to Matz.

Make it available at the toplevel (and everywhere else):

class Object
include M
end

p val # => nil
val "main"
p val # => "main"

It's also available at the class level:

class Q
p val # => nil
val "Q"
p val # => "Q"
end

HTH

Regards,
Pit
 
T

Trans

Pit,



Pit said:
Tom, I think I still don't get what your problem is. If you want to use
a module at the toplevel just include it in Object.

The module is for module/class level. It uses #define_method and
#ancestors. Including it into Object just gives:

./taskable.rb:184:in `task': undefined method `define_method' for
main:Object (NoMethodError)

Using extend makes the methods not available at the toplevel.

I pasted the code base I'm working on below. To try it, create a task
and call it:

task :foo do
puts "foo"
end

foo

T.


# = taskable.rb
#
# == Copyright (c) 2006 Thomas Sawyer
#
# Ruby License
#
# This module is free software. You may use, modify, and/or
redistribute this
# software under the same terms as Ruby.
#
# This program is distributed in the hope that it will be useful, but
WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# == Auhtor(s)
#
# * Thomas Sawyer

# Author:: Thomas Sawyer
# Copyright:: Copyright (c) 2006 Thomas Sawyer
# License:: Ruby License

require 'tsort'

# = TSort
#
# TSort is used to determine which prerequisites to run
# for a task.
#
# Here we add a convenience method --if such a long method
# name can be said to be convenient ;) NOTE: Although I ended
# up not using this after all --it still may proved useful and
# may eventually be added to Facets or submitted as a patch.

module TSort
def strongly_connected_components_from(node)
result = []
each_strongly_connected_component_from(node) do |component|
result << component
end
result
end
end

# = Task
#
# A Task is a Method with prerequisite dependencies.
#
# NOTE Can't subclass Method b/c Method has no initiator.
# Not sure that would be a good idea anyway.

class Task
attr_accessor :description

def initialize( name, container, preq, desc=nil, &action )
@name = name
@container = container
@prerequisite = preq || []
@description = desc
@action = action
end

def prerequisite
@prerequisite ||= []
end

# #--
# # TODO Problem is 'a' can't take any parameters,
# # and there's no way to sort the tasks
# # from here. FIX if possible.
# #++
# def to_proc
# r = @prerequisite
# n = @name
# a = @action
# lambda {
# r.each{ |d| send(d) unless n == d }
# instance_eval &a if a
# }
# end

def complete
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are singletons?
t = Graph.new( container, name )
t.each_strongly_connected_component_from( name ) do |c|
#@container.instance_task(c).call
@container.send("->#{d}")
end
end

def call
@action.call
end

def each(&blk)
@prerequisite.each(&blk)
end

# Task::Graph is a Tarjan-sorted hash.
# It is used to generate the proper dependency chains.
# TODO Make Multiton off of base?

class Graph < Hash
include TSort

def initialize( base, name )
@base = base.class
@name = name
instance_map( name )
end

def instance_map( name )
t = @base.instance_task(name)
raise "task does not exist -- #{name} in #{@base}" unless t
self[name] = t.prerequisite || []
self[name].each { |r| instance_map( r ) }
end

alias_method :tsort_each_node, :each_key

def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end

end

# = Taskable
#
# The Taskable module provides a generic task system
# patterned after Rake, but useable in any
# code context --not just with the Rake tool. In other
# words one can create methods with dependencies.
#
# NOTE Unlike methods, tasks can't take parameters
# if they are to be used as prerequisites. They can
# pass on the parameters of the original call though.

module Taskable

# Define description for subsequent task.

def desc(line=nil)
return @last_description unless line
@last_description = line.gsub("\n",'')
end

# Use up the description for subsequent task.

def desc!
l = @last_description
@last_description = nil
l
end

# Define a task.

def task(args, &action)
if Hash === args
raise ArgumentError, "#{args.size} for 1" if args.size != 1
name, *deps = *(args.to_a.flatten)
name = name.to_sym
deps = deps.compact.collect{ |e| e.to_sym }
else
name, deps = args.to_sym, []
end
# create task by creating core and callable methods.
# We do it this way b/c otherwise we'd have to use
# instance_eval and then can't pass paramaters (TODO fix w/Ruby
1.9).
if action
define_method( "->#{name}", &action ) # core of the task
define_method( name ) do |*args|
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are
singletons?
todolist = Task::Graph.new( self, name )
todolist.each_strongly_connected_component_from( name ) do |c|
send("->#{c}", *args)
end
end
instance_task_table[name] = Task.new( name, self, deps, desc!,
&action )
elsif m = instance_task(name)
#m.description = desc! # TODO should this apply only if there is
an action?
m.prerequisite.concat deps unless deps.empty?
m
else
#define_method( "->#{name}", lambda{} ) # empty action
define_method( name ) do |*args|
# ensure no cycles in the graph and extract call graph
#own = (class << self; self; end) # in case there are
singletons?
todolist = Task::Graph.new( self, name )
todolist.each_strongly_connected_component_from( name ) do |c|
send("->#{c}", *args) unless "#{c}" == "#{name}"
end
end
instance_task_table[name] = Task.new( name, self, deps, desc!,
&action )
end
end

# Access a task.

def instance_task( name )
name = name.to_sym
if instance_task_table and r = instance_task_table[name]
return r
end
ancestors.each do |anc|
itt = anc.instance_task_table
#instance_variable_get("@instance_task_table")
if itt and r = itt[name]
return r
end
end
nil
end

# List of task names.

def instance_tasks( include_ancestors=true )
if include_ancestors and ancestors and ancestors[1]
instance_task_table.keys | ancestors[1].instance_tasks
else
instance_task_table.keys
end
end

protected

def instance_task_table
@instance_task_table ||= {}
end

end

class Module
include Taskable
end

include Taskable
 
L

Logan Capaldo

Trans said:
Unfortuately when I use this to make a Rake command-line emulator I
want to use the module at the toplevel and can't.

Tom, I think I still don't get what your problem is. If you want to
use a module at the toplevel just include it in Object.

The module:

module M
def val *args
if args.empty?
@val
else
@val = args[ 0 ]
end
end
end

I used some instance variables here, because you wrote about them
in the answer to Matz.

Make it available at the toplevel (and everywhere else):

class Object
include M
end

p val # => nil
val "main"
p val # => "main"

It's also available at the class level:

class Q
p val # => nil
val "Q"
p val # => "Q"
end

HTH

Regards,
Pit

You are over simplifying Trans.'s problem. He wants dynamic generated
methods scoped to a module, or optionally scoped to the top level.
You can't use #define_method at the top level because instances don't
respond to it. You can't use the toplevel singleton class as I
suggested because a) it's a pain in the neck and b) it doesn't
inherit the right way.


Trans, what about:

# Warning evilness ensuing:

def main.define_method(*args, &block)
Kernel.module_eval{ define_method(*args, &block) }
end
 
T

Trans

Logan said:
You are over simplifying Trans.'s problem.

Yea Pit, why are you doing that? ;-)
He wants dynamic generated
methods scoped to a module, or optionally scoped to the top level.
You can't use #define_method at the top level because instances don't
respond to it. You can't use the toplevel singleton class as I
suggested because a) it's a pain in the neck and b) it doesn't
inherit the right way.

Trans, what about:

# Warning evilness ensuing:

def main.define_method(*args, &block)
Kernel.module_eval{ define_method(*args, &block) }
end

That's basically what I ended up doing:

def define_method( meth, &block )
Object.class_eval do
define_method( meth, &block )
private meth
end
end

def ancestors
Object.ancestors
end

But as I was saying about the instance variables, it not quite that
simple. I still had to build exceptions into the module code. I just
tried to factor these down to the fewest points of departure as
possible. So I end up with a few areas like this:

$main = self

def instance_task_table
if self == $main
Object.instance_task_table
else
@instance_task_table ||= {}
end
end

and

cont = (self == $main ? Object : self )

Certainly not simple. But so far it appears to be working. I just hope
I've got all the namespace dicing covered.

Thanks,
T.
 
T

Trans

Trans said:
Why? Becuase the module uses #define_method, and #define_method isn't
defined in main.

Which brings me to two questions:

1. How do I get around this problem? Please save me from having to
delgate the whole module!

2. Why isn't main a self extended module to begin with? Honestly, this
question has been buigging me for a long time.

T.

As a follow up to this, I made the following blog post:


http://7rans0noma.blogspot.com/2006/08/vote-kernel-for-toplevel-object.html

T.
 
T

Trans

Yukihiro said:
Hi,

In message "Re: using a module at the toplevel doesn't work"

|http://7rans0noma.blogspot.com/2006/08/vote-kernel-for-toplevel-object.html

I don't feel that making Kernel as toplevel self is not a good idea,
because:

* toplevel def does not define methods on Kernel, but Object.
* toplevel include does not include modules into Kernel, but Object.
* toplevel private etc. do not work on Kernel, but Object.

To keep the namespace separate I take it? In general one can hardly
tell them apart. I know I was very suprised when I discoverd that
Object starts out completely devoid of any methods. And I suspect now,
that toplevel's effect on Object is the reason why.

So by what you say I would be inclined to wonder why isn't toplevel
Object itself rather than a special instance of Object. But the reaon
it cannot be Object is because Object can not 'extend self' like
toplevel must.

So if not Kernel and also not Object, what of the merits of separate
module Main, included in Object.

SomeClass.ancestors => [ Object, Main, Kernel ]

So that what I am trying to undersatnd. Why would you go to the trouble
to make the toplevel a (partial) proxy of Object when a self extended
module would prioved complete and seemless functionality?

Thanks,
T.
 
T

Trans

Well, is there anyway to turn toplevel effects on Object off? I have a
real scenario where it causes issues.

module X
def self.method_missing( name, *args )
if name ...
if require 'foo'
send( name, *args )
...

Now if some method is defined at toplevel it can screw up my module X
lazy loader.

T.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: using a module at the toplevel doesn't work"

|Well, is there anyway to turn toplevel effects on Object off? I have a
|real scenario where it causes issues.
|
| module X
| def self.method_missing( name, *args )
| if name ...
| if require 'foo'
| send( name, *args )
| ...
|
|Now if some method is defined at toplevel it can screw up my module X
|lazy loader.

Can you show me working (well, I mean non-working) example. I am not
sure how above example relates to toplevel effects.

matz.
 
T

Trans

Yukihiro said:
Hi,

In message "Re: using a module at the toplevel doesn't work"

|Well, is there anyway to turn toplevel effects on Object off? I have a
|real scenario where it causes issues.
|
| module X
| def self.method_missing( name, *args )
| if name ...
| if require 'foo'
| send( name, *args )
| ...
|
|Now if some method is defined at toplevel it can screw up my module X
|lazy loader.

Can you show me working (well, I mean non-working) example. I am not
sure how above example relates to toplevel effects.

Sure. I have a bunch of build scripts that can be resused in other
scripts. Typcially people will reuse then threw one-off scripts that
simple define methods at the top level. E.g. something like

def test
Script.test
end

Now the Script module uses lazy loading in order to keep it fast --no
sense in loading module's you don't need:

module Script

extend self

# Some tasks belong to variant scripts.

TASK2SCRIPT = {
:version => :revision,
:changelog => :revision
}

# When a built-in task is called it's script is dynamically loaded.

def method_missing( meth, *args, &blk )
file = TASK2SCRIPT[meth] || meth
begin
require "sake/#{file}"
if respond_to?(meth)
send( meth, *args, &blk )
else
super
end
rescue LoadError
super
end
end

end

As you can see my particlar 'test' example will cause an infinite loop
because the toplevel definition is being added to all objects. :(

Thanks for looking at this,
T.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top