Thread: [Rubydotnet-developer] Getting necessary metadata on Ruby classes for externalization...
Status: Alpha
Brought to you by:
thomas
From: Richard K. <ri...@in...> - 2003-07-19 03:54:33
|
All, I did not quite know where to post this, and I hope this list is ok... I have been thinking a bit on how to allow a developer to annotate their class's methods with parameter types when used externally by statically typed languages (Java, C#, SOAP). I want to present a syntax, and at the bottom have the code to back it up. Given the following class: class Account def Account.open(first, last) end def close end def add(money) end def remove(money) end def transfer(money, account) end end And assuming you wanted to make that accessible for statically typed systems you would just do this: require 'extern' class Account extern :Account.open, :return[Account], :first[String], :last[String] def Account.open(first, last) end extern :close def close end extern :add, :money[Float] def add(money) end extern :remove, :money[Float] def remove(money) end extern :transfer, :money[Float], :account[Account] def transfer(money, account) end end So, for each method you want to externalize you just include the 'extern' call. Usage: extern <method symbol>, [ [<:return[Classname]>], <:param[Classname]>, ... ] To reflect on this metadata you do: Account.each_externalized_method do | method | puts method.to_s end Which outputs: Account Account.open(String first, String last) NilClass close() NilClass add(Float money) NilClass remove(Float money) NilClass transfer(Float money, Account account) You can, of course, inspect the method (ExternalMethodDefinition instance) instead of printing it out. So this creates a runtime structure to store type information about methods, which again is useful is you want to bridge .NET to Ruby at the level of having a CLR type subclass a Ruby class, or generate a CLR type that represents a Ruby class. Of course, since Ruby's classes are open, you can define this extern metadata on existing classes: class ThreadGroup extern :ThreadGroup.new, :return[ThreadGroup] extern :add, :return[ThreadGroup], :thread[Thread] extern :list, :return[Array] end So what do folks think about the syntax? -rich PS...here is the magic code that makes this syntax work: ________ BEGIN 'extern.rb' _________ class ExternalMethodDefinition attr_accessor :klass, :name, :return_type Parameter = Struct.new(:name, :type) @@definitions = Hash.new([]) def self.[](klass) @@definitions[klass] end def initialize(klass, name, *parameters) list = @@definitions[klass] unless list list = [] @@defintions[klass] = list end list << self @klass = klass @name = name.to_s @name.gsub!(/__/, '.') @parameters = [] parameters.each do |param| if param.name=="return" @return_type = param.type else @parameters << param end end @return_type = NilClass unless @return_type end def add_parameter(name, type) unless type.kind_of?(Class) raise "Parameter #{name}'s type on method '#{@name}' of class '#{@klass}' must be a Ruby class" end if name == :return @return_type = type else @parameters << Parameter.new(name.to_s, type) end end def each_parameter @parameters.each {|param| yield param} end def to_s params = (@parameters.collect {|param| param.type.to_s+' '+param.name}).join(', ') return "#{@return_type.to_s} #{@name}(#{params})" end end class Symbol def [](klass=nil) if klass ExternalMethodDefinition::Parameter.new(self.to_s, klass) else (self.to_s+"__[]").intern end end def method_missing(method, *args, &block) if (?A..?Z).member?(self.to_s[0]) return (self.to_s+"__"+method.to_s).intern end super end end class Module def extern(method, *parameters) ExternalMethodDefinition.new(self, method, *parameters) end def each_externalized_method ExternalMethodDefinition[self].each { |exdef| yield exdef } end end ------ END 'extern.rb' ------ |
From: Thomas S. <th...@th...> - 2003-07-21 14:29:07
|
> So what do folks think about the syntax? I'd prefer the syntax to be as terse as possible and with as little duplicate information as possible. In other words, I'd prefer extern :Account.open, Account, String, String def Account.open(first, last) end to extern :Account.open, :return[Account], :first[String], :last[String] def Account.open(first, last) end But I don't know how to get the names of the arguments? The Module methods to iterate over methods simply return strings. So I'm not sure we can do better, than what you suggest, and it's not bad at all, but maybe we should suggest to matz that it would be convenient if you could get more information at runtime about the signature of a method? Cheers, Thomas ---------- Original Message ----------- From: Richard Kilmer <ri...@in...> To: rub...@li... Sent: Fri, 18 Jul 2003 23:54:29 -0400 Subject: [Rubydotnet-developer] Getting necessary metadata on Ruby classes for externalization... > All, > > I did not quite know where to post this, and I hope this list is ok... > > I have been thinking a bit on how to allow a developer to annotate > their class's methods with parameter types when used externally by > statically typed languages (Java, C#, SOAP). I want to present a > syntax, and at the bottom have the code to back it up. > > Given the following class: > > class Account > def Account.open(first, last) > end > def close > end > def add(money) > end > def remove(money) > end > def transfer(money, account) > end > end > > And assuming you wanted to make that accessible for statically typed > systems you would just do this: > > require 'extern' > > class Account > extern :Account.open, :return[Account], :first[String], :last[String] > def Account.open(first, last) > end > > extern :close > def close > end > > extern :add, :money[Float] > def add(money) > end > > extern :remove, :money[Float] > def remove(money) > end > > extern :transfer, :money[Float], :account[Account] > def transfer(money, account) > end > end > > So, for each method you want to externalize you just include the > 'extern' call. > > Usage: extern <method symbol>, [ [<:return[Classname]>], > <:param[Classname]>, ... ] > > To reflect on this metadata you do: > > Account.each_externalized_method do | method | > puts method.to_s > end > > Which outputs: > > Account Account.open(String first, String last) > NilClass close() > NilClass add(Float money) > NilClass remove(Float money) > NilClass transfer(Float money, Account account) > > You can, of course, inspect the method (ExternalMethodDefinition > instance) instead of printing it out. So this creates a runtime > structure to store type information about methods, which again is > useful is you want to bridge .NET to Ruby at the level of having a > CLR type subclass a Ruby class, or generate a CLR type that > represents a Ruby class. > > Of course, since Ruby's classes are open, you can define this extern > metadata on existing classes: > > class ThreadGroup > extern :ThreadGroup.new, :return[ThreadGroup] > extern :add, :return[ThreadGroup], :thread[Thread] > extern :list, :return[Array] > end > > So what do folks think about the syntax? > > -rich > > PS...here is the magic code that makes this syntax work: > > ________ BEGIN 'extern.rb' _________ > > class ExternalMethodDefinition > attr_accessor :klass, :name, :return_type > Parameter = Struct.new(:name, :type) > @@definitions = Hash.new([]) > > def self.[](klass) > @@definitions[klass] > end > > def initialize(klass, name, *parameters) > list = @@definitions[klass] > unless list > list = [] > @@defintions[klass] = list > end > list << self > @klass = klass > @name = name.to_s > @name.gsub!(/__/, '.') > @parameters = [] > parameters.each do |param| > if param.name=="return" > @return_type = param.type > else > @parameters << param > end > end > @return_type = NilClass unless @return_type > end > > def add_parameter(name, type) > unless type.kind_of?(Class) > raise "Parameter #{name}'s type on method '#{@name}' of class > '#{@klass}' must be a Ruby class" > end > if name == :return > @return_type = type > else > @parameters << Parameter.new(name.to_s, type) > end > end > > def each_parameter > @parameters.each {|param| yield param} > end > > def to_s > params = (@parameters.collect {|param| param.type.to_s+' > '+param.name}).join(', ') > return "#{@return_type.to_s} #{@name}(#{params})" > end > end > > class Symbol > def [](klass=nil) > if klass > ExternalMethodDefinition::Parameter.new(self.to_s, klass) > else > (self.to_s+"__[]").intern > end > end > > def method_missing(method, *args, &block) > if (?A..?Z).member?(self.to_s[0]) > return (self.to_s+"__"+method.to_s).intern > end > super > end > end > > class Module > def extern(method, *parameters) > ExternalMethodDefinition.new(self, method, *parameters) > end > > def each_externalized_method > ExternalMethodDefinition[self].each { |exdef| yield exdef } > end > end > > ------ END 'extern.rb' ------ > > ------------------------------------------------------- > This SF.net email is sponsored by: VM Ware > With VMware you can run multiple operating systems on a single machine. > WITHOUT REBOOTING! Mix Linux / Windows / Novell virtual machines at the > same time. Free trial click here: > http://www.vmware.com/wl/offer/345/0 _______________________________________________ > Rubydotnet-developer mailing list > Rub...@li... > https://lists.sourceforge.net/lists/listinfo/rubydotnet-developer ------- End of Original Message ------- |