Interfaces and semantics (or, how to hashpipe a duck)

H

Hal Fulton

Semantics, James Thurber notwithstanding, is not a town in Ohio.

I think when Rich says "semantics" he means something that can't
really be checked by a machine -- the "purpose or function," as it
were.

I do sometimes wish there were a way to detect semantics -- but the
best we could do is simply remove the problem a step further.

Warning: I'm tired, so I'm not expressing myself as well as I might
at a different time of day.


For example, imagine that we are concerned with a conceptual "append"
operation. We want to know whether an object is capable of being
appended onto.

Well, a string is "appendable." So is an array. So is a file.

str << x
arr << x
file << x

Aha! Why not just check for << existing?

Oops. There's a Fixnum#<< which does a left shift!

In short, the first three have very similar semantics, but the
last is very different.

I have at times (almost) wished for a thing rather like Java interfaces:

obj.implements?(Append) # where Append is some sort of interface
# whose conceptual semantics are agreed on

Sure, modules in Ruby can serve that purpose. But it's rather
inefficient to have 10-15 modules included in a class (especially when
most methods will be redefined anyway).

But even if we had #implements? -- would it really solve anything? Not
necessarily. We would still have to deal with programmers who were
careless, ignorant, or misinterpreted the docs.

Just the random firings of my neurons. Ignore at your leisure.


Hal Fulton
 
D

David A. Black

Hi --

Semantics, James Thurber notwithstanding, is not a town in Ohio.

I think when Rich says "semantics" he means something that can't
really be checked by a machine -- the "purpose or function," as it
were.

Yeah, I think you're right. I'd been interpreting the term as a kind
of collective reference to things like signature, return type, etc.
(which is why I'd had trouble finding any of them in Ruby methods :)


David
 
S

Sean O'Dell

Semantics, James Thurber notwithstanding, is not a town in Ohio.

I think when Rich says "semantics" he means something that can't
really be checked by a machine -- the "purpose or function," as it
were.

I do sometimes wish there were a way to detect semantics -- but the
best we could do is simply remove the problem a step further.

Here's what I'm working on right now for my own purposes. I've already
applied for a RubyForge project so if others find this useful, they can use
it too. Take a look:

# first class defines the interface
class MyClass
def method1(a, b, c)
end

def method2(a, b, c = nil)
end

define_interface:)my_interface, :method1, :method2)
end

# of course it has its own interface
a = MyClass.new
a.has_interface?:)my_interface) => true

# second class simply declares that it implements the interface
class UnrelatedClass
declare_interface:)my_interface)

def method1(a, b, c)
end

def method2(a, b, c = nil)
end
end

# it also implements the interface
a = UnrelatedClass.new
a.has_interface?:)my_interface) => true

# the third class declares the interface
# but changes one of the methods' parameters to have no default
class NoncompliantClass
declare_interface:)my_interface)

def method1(a, b, c)
end

def method2(a, b, c)
end
end

# because of the method change, it does not implement the interface
a = NoncompliantClass.new
a.has_interface?:)my_interface) => false



I'm writing a little lib in C to do this right now. I'm mostly done. I am
fighting with the Ruby C API's class variable access, otherwise I'd be
finished.

Sean O'Dell
 
N

nobu.nokada

Hi,

At Tue, 8 Jun 2004 12:36:28 +0900,
Sean O'Dell wrote in [ruby-talk:102735]:
I'm writing a little lib in C to do this right now. I'm mostly done. I am
fighting with the Ruby C API's class variable access, otherwise I'd be
finished.

See variable.c:

VALUE rb_cvar_defined _((VALUE, ID));
#define RB_CVAR_SET_4ARGS 1
void rb_cvar_set _((VALUE, ID, VALUE, int));
VALUE rb_cvar_get _((VALUE, ID));
void rb_cv_set _((VALUE, const char*, VALUE));
VALUE rb_cv_get _((VALUE, const char*));
void rb_define_class_variable _((VALUE, const char*, VALUE));
VALUE rb_mod_class_variables _((VALUE));
VALUE rb_mod_remove_cvar _((VALUE, VALUE));
 
D

David Garamond

Let's say I want to implement interfaces using classes (so I can do
interface checking with the simple Object#kind_of?). There will be
components (which will also be classes). A component can implement one
or more interfaces. Method names from different interfaces might clash
(e.g. IFoo#quack vs IBar#quack) and we need to be able to choose which
interface we want to use from a component.

What's the best way to implement this? My current attempt:

--- BEGIN OF CODE ---
# components will derive from the Component class, to share
# some common code
class Component

# @implements will contain a list of interfaces that this component
# implements
def Component.implements; @implements end
def implements; self.class.implements end
def Component.implements?(itf); @implements.include? itf end
def implements?(itf); self.class.implements? itf end

# @implementors is a hash, containing the class that implements a
# certain interface.
def Component.implementors; @implementors end
def implementors; self.class.implementors end

# will return an instance of implementor class
def use_interface(itf, *args)
raise ArgumentError unless implements? itf
raise NotImplementedError unless implementors[itf]
implementors[itf].new(*args)
end
end

# IHasFeet is an interface
class IHasFeet
# s is speed
def walk(s); raise NotImplementedError end

# d is the name of a dance
def entertain(d); raise NotImplementedError end
end

# IHasMouth is an interface
class IHasMouth
# sp is speed, str is string
def talk(sp, str); raise NotImplementedError end

# s is the name of a song
def entertain(s); raise NotImplementedError end
end

# Human is a component. It implements IHasFeet and IHasMouth.
class Human < Component
@implements = [IHasFeet, IHasMouth]

class Human_IHasFeet < IHasFeet
def walk(s); puts "Walking..." end
def entertain(d); puts "Can't dance!" end
end

class Human_IHasMouth < IHasMouth
def talk(sp, str); puts str end
def entertain(s); puts "Singing #{s}..." end
end

@implementors = {IHasFeet => Human_IHasFeet,
IHasMouth => Human_IHasMouth}
end

# Bear is a component. It implements IHasFeet and IHasMouth.
class Bear < Component
@implements = [IHasFeet, IHasMouth]

class Bear_IHasFeet < IHasFeet
def walk(s); puts "Walking..." end
def entertain(d); puts "Dancing #{d}..." end
end

class Bear_IHasMouth < IHasMouth
def talk(sp, str); puts "R O A R!" end
def entertain(s); puts "Can't sing!" end
end

@implementors = {IHasFeet => Bear_IHasFeet,
IHasMouth => Bear_IHasMouth}
end

# test

p Bear.implements # -> [IHasFeet, IHasMouth]
p Bear.implements? IHasMouth # -> true

bear = Bear.new
p bear.implements # -> [IHasFeet, IHasMouth]
p bear.implements? IHasFeet # -> true

b = bear.use_interface(IHasFeet)
p b.kind_of? IHasFeet # -> true
b.walk(1) # -> Walking...
b.entertain("russian dance") # -> Dancing russian dance...
b = bear.use_interface(IHasMouth)
b.entertain("o canada") # -> Can't dance!
--- END OF CODE ---

The code feels very mechanic and contains a lot of plumbing, but it's
pretty much what I wanted (my requirements):

a) a component can implement multiple interfaces;
b) I can pick interfaces from components without fear of conflict;
c) my other code can later check whether something has a feet or a
mouth, without explicitly checking its component class (Human or Bear).

Any better suggestion?
 
G

gabriele renzi

il Tue, 8 Jun 2004 17:47:08 +0900, David Garamond
Let's say I want to implement interfaces using classes (so I can do
interface checking with the simple Object#kind_of?). There will be
components (which will also be classes). A component can implement one
or more interfaces. Method names from different interfaces might clash
(e.g. IFoo#quack vs IBar#quack) and we need to be able to choose which
interface we want to use from a component.

take a look at PyProtocols,
http://peak.telecommunity.com/PyProtocols.html

may be of interest to you. Imo, it's a complex solution to a complex
problem, I'd prefer less general and easyer.
 
D

David Garamond

gabriele said:
take a look at PyProtocols,
http://peak.telecommunity.com/PyProtocols.html

may be of interest to you. Imo, it's a complex solution to a complex
problem, I'd prefer less general and easyer.

I took a look at PEAK & PyProtocols a few weeks ago. I've also seen
Zope3's component architecture some months ago. I heard they will
somehow merge their interface/component architecture.

To tell you the truth, I don't like the way they do it. IIRC, Zope3
implements interface as a normal object (instead of a class). Also their
notion of a 'component' is not like what I have in mind (which is more
like COM's or CORBA's component). Zope's component is more like a
'component instance' instead.

But I agree with you about simpler solution. Interfaces (at least in
Java) are basically another hierarchy of class-like/type-like things,
separate from the class hierarchy. We just need to define interfaces
and tag classes ("components") with them. Classes are the entity closest
to interfaces, so I tend to define interface as a Ruby class.
 
J

Jim Weirich

David said:
Hi --




Yeah, I think you're right. I'd been interpreting the term as a kind
of collective reference to things like signature, return type, etc.
(which is why I'd had trouble finding any of them in Ruby methods :)

Method semantics are about *what* a method does. For a class to be a
subtype (i.e. Duck type-able ... is that a word?), it must obey the
Liskov Substitution Principle (LSP). In general that means (1) the
method names need to be the same, (2) they take the same arguments and
(3) they have compatible (but not necessarily identical) semantics.
Using respond_to? just checks (1).

I don't have time this morning to say more than this, but I recommend
the white papers at objectmentor.com as a starting place for reading
about LSP and other design principles. Also
http://archive.eiffel.com/doc/manuals/technology/contract/ is a good
beginner's article on Contracts (which is one way of actually specifying
semantics).
 
D

Daniel Berger

Sean O'Dell said:
Here's what I'm working on right now for my own purposes. I've already
applied for a RubyForge project so if others find this useful, they can use
it too. Take a look:

# first class defines the interface
class MyClass
def method1(a, b, c)
end

def method2(a, b, c = nil)
end

define_interface:)my_interface, :method1, :method2)
end

# of course it has its own interface
a = MyClass.new
a.has_interface?:)my_interface) => true

# second class simply declares that it implements the interface
class UnrelatedClass
declare_interface:)my_interface)

def method1(a, b, c)
end

def method2(a, b, c = nil)
end
end

# it also implements the interface
a = UnrelatedClass.new
a.has_interface?:)my_interface) => true

# the third class declares the interface
# but changes one of the methods' parameters to have no default
class NoncompliantClass
declare_interface:)my_interface)

def method1(a, b, c)
end

def method2(a, b, c)
end
end

# because of the method change, it does not implement the interface
a = NoncompliantClass.new
a.has_interface?:)my_interface) => false



I'm writing a little lib in C to do this right now. I'm mostly done. I am
fighting with the Ruby C API's class variable access, otherwise I'd be
finished.

Sean O'Dell

I published an "interface" module a short while back. You can find it
on the RAA or at:

http://ruby-miscutils.sourceforge.net

Regards,

dan
 
S

Sean O'Dell

Hi,

At Tue, 8 Jun 2004 12:36:28 +0900,

Sean O'Dell wrote in [ruby-talk:102735]:
I'm writing a little lib in C to do this right now. I'm mostly done. I
am fighting with the Ruby C API's class variable access, otherwise I'd be
finished.

See variable.c:

VALUE rb_cvar_defined _((VALUE, ID));
#define RB_CVAR_SET_4ARGS 1
void rb_cvar_set _((VALUE, ID, VALUE, int));
VALUE rb_cvar_get _((VALUE, ID));
void rb_cv_set _((VALUE, const char*, VALUE));
VALUE rb_cv_get _((VALUE, const char*));
void rb_define_class_variable _((VALUE, const char*, VALUE));
VALUE rb_mod_class_variables _((VALUE));
VALUE rb_mod_remove_cvar _((VALUE, VALUE));

I have the docs. What's wrong is, in an instance method, I can't seem to
access the class variable from the C API. In Ruby, instance methods can see
the instance variables just fine, so I know that part of my code is working
with them right.

Sean O'Dell
 
S

Sean O'Dell

I published an "interface" module a short while back. You can find it
on the RAA or at:

Mine looks pretty close to how yours works except it verifies the number of
method arguments. Whenever a method is added or removed, the compliance flag
is lowered so the next time has_interface? is called, a complete check is
re-done to be sure the object still complies. Is yours written in C? One
thing I wanted was for this to have as little overhead as possible, so I
didn't feel like I was slowing down my project too much by calling it
everywhere.

Sean O'Dell
 
N

nobu.nokada

Hi,

At Tue, 8 Jun 2004 23:32:28 +0900,
Sean O'Dell wrote in [ruby-talk:102779]:
I have the docs. What's wrong is, in an instance method, I can't seem to
access the class variable from the C API. In Ruby, instance methods can see
the instance variables just fine, so I know that part of my code is working
with them right.

VALUE cvar = rb_cvar_get(CLASS_OF(obj), rb_intern("@@cvar"));

and so on.
 
S

Sean O'Dell

Hi,

At Tue, 8 Jun 2004 23:32:28 +0900,

Sean O'Dell wrote in [ruby-talk:102779]:
I have the docs. What's wrong is, in an instance method, I can't seem to
access the class variable from the C API. In Ruby, instance methods can
see the instance variables just fine, so I know that part of my code is
working with them right.

VALUE cvar = rb_cvar_get(CLASS_OF(obj), rb_intern("@@cvar"));

What I'm doing is setting the class variables in the class definition, then
trying to use them in an instance method. In Ruby, the instance methods can
see the class variables just fine. In C, the API says they don't exist.

Sean O'Dell
 
R

Richard Kilmer

Hi,

At Tue, 8 Jun 2004 23:32:28 +0900,

Sean O'Dell wrote in [ruby-talk:102779]:
I have the docs. What's wrong is, in an instance method, I can't seem to
access the class variable from the C API. In Ruby, instance methods can
see the instance variables just fine, so I know that part of my code is
working with them right.

VALUE cvar = rb_cvar_get(CLASS_OF(obj), rb_intern("@@cvar"));

What I'm doing is setting the class variables in the class definition, then
trying to use them in an instance method. In Ruby, the instance methods can
see the class variables just fine. In C, the API says they don't exist.

Sean O'Dell

If you are in an instance method in C you get the obj as the first param.
The code above references the CLASS_OF your obj and gets its class variable
@@whatever. Isn't that what you want?
 
S

Sean O'Dell

Hi,

At Tue, 8 Jun 2004 23:32:28 +0900,

Sean O'Dell wrote in [ruby-talk:102779]:
I have the docs. What's wrong is, in an instance method, I can't seem to
access the class variable from the C API. In Ruby, instance methods can
see the instance variables just fine, so I know that part of my code is
working with them right.

VALUE cvar = rb_cvar_get(CLASS_OF(obj), rb_intern("@@cvar"));

Oh! I see what you mean now; I overlooked the CLASS_OF part. Thanks, I'll
try that.

Sean O'Dell
 
F

Florian Gross

Hal said:
I think when Rich says "semantics" he means something that can't
really be checked by a machine -- the "purpose or function," as it
were.
I do sometimes wish there were a way to detect semantics -- but the
best we could do is simply remove the problem a step further.
For example, imagine that we are concerned with a conceptual "append"
operation. We want to know whether an object is capable of being
appended onto.

Well, a string is "appendable." So is an array. So is a file.
I have at times (almost) wished for a thing rather like Java interfaces:

obj.implements?(Append) # where Append is some sort of interface
# whose conceptual semantics are agreed on

I think I'm going to share my ideas about all this. Oh, and sorry for
the heavy quoting, but it's hard to snip well-written text. :)

However this also applies to me right now:

I would really like to do it like this:

EachContract = Contract.new do
def test(obj)
assert(obj.respond_to?:)each))
end
end

AppendContract = Contract.new do
implicates EachContract

def test_append(obj)
assert(obj.respond_to?:)<<))
initial = []; obj.each { |item| initial << item }
new_obj = obj.clone; new_obj << "x"
appended = []; obj.each { |item| appended << item }
assert(appended.size > initial.size)
assert(initial.all? { |item| appended.include?(item) })
end
end

obj = []
case obj
when AppendContract then obj << 5
end

This still needs to be thought to the end, however.

Regards,
Florian Gross
 
D

Daniel Berger

Sean O'Dell said:
Mine looks pretty close to how yours works except it verifies the number of
method arguments. Whenever a method is added or removed, the compliance flag
is lowered so the next time has_interface? is called, a complete check is
re-done to be sure the object still complies. Is yours written in C? One
thing I wanted was for this to have as little overhead as possible, so I
didn't feel like I was slowing down my project too much by calling it
everywhere.

Sean O'Dell

It's pure Ruby. I don't think adding arity checks and hooks for
method_added/method_removed would be difficult.

Regards,

Dan
 
S

Sean O'Dell

It's pure Ruby. I don't think adding arity checks and hooks for
method_added/method_removed would be difficult.

Probably wouldn't, but I just finished mine and it's released, and it's in C.
I don't know what to do about the name clash. I can't think of another name
that works as well, and I have to move on to some actual work here.

Sean O'Dell
 
D

David A. Black

Hi --

Probably wouldn't, but I just finished mine and it's released, and it's in C.
I don't know what to do about the name clash. I can't think of another name
that works as well, and I have to move on to some actual work here.

It's hard to think of a name that works *less* well, since this one
clashes with an already-available package. I hope you'll consider
renaming it, as a courtesy to Dan and to reduce confusion by potential
users.


David
 
S

Sean O'Dell

Hi --



It's hard to think of a name that works *less* well, since this one
clashes with an already-available package. I hope you'll consider
renaming it, as a courtesy to Dan and to reduce confusion by potential
users.

I will when I have a suitable alternative.

Sean O'Dell
 

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

No members online now.

Forum statistics

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

Latest Threads

Top