Meta-Meta-Programming

E

Erik Veenstra

I had a discussion with a friend. A Java guy. He wants the
arguments of a method call being checked. "I want the first one
to be an Integer. And the second one is a String. Period." No
discussion. I explained our duck-typing paradigm. He's not
convinced. He thinks Java. So, he gets Java.

Lets check the types of the arguments of a method call!

(This post is not about type checking at all. It's about how to
implement such a type checker. Or, more general, it's about
monitoring-functions.)

I wanted to do this with a nice and clean implementation, with
the real magic pushed down to a place I would never come again
("write once, read never"). I wanted something like this (focus
on line 7):

1 class Foo
2 def bar(x, y, z)
3 # x should be Numeric
4 # y should be a String
5 # z should respond to :to_s
6 end
7 typed :bar, Numeric, String, :to_s # !!!!!
8 end

Focus on line 7, once again. Make it three times. It's all
about line 7.

That was good enough for him. "But you can't do this. You
simply can't. That's magic." I laughed at him, turned around
and did it...

That's where this story is all about...

First, I'll give you a piece of code which doesn't do anything,
except that it seems to wrap the original method in another
method (focus on line 12):

1 class Module
2 def just_wrap(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 org_method.call(*args, &block)
5 end
6 end
7 end
8 class Foo
9 def bar(x, y, z)
10 p [x, y, z]
11 end
12 just_wrap :bar # !!!!!
13 end
14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"]

You can find the implementation of wrap_method below. This
thread is all about that very one method. It's the big trick.
You don't need to understand its implementation. Knowing how to
use it is good enough.

Line 3 retrieves the original method and yields the given block
with this method, as well as with its arguments and block. Not
*args, not &block. Just args and block. Blocks don't get
blocks, you know. (Although it's introduced in Ruby 1.9.)

Within the given block, we can do whatever we want to. That's
where the real stuff goes.

But, someday, we have to call the original method with the
original parameters and the original block. That's what we do
on line 4.

That's about it. That's the whole story. There's nothing more
to say.

Except for an example or two...

Here's a simple example. It "upcases" every argument. It must
be silly to "upcase" every argument like this, but we'll do it
anyway. Introducing line 4:

1 class Module
2 def big_arguments(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 args = args.collect{|x| x.to_s.upcase}
5 org_method.call(*args, &block)
6 end
7 end
8 end
9 class Foo
10 def bar(x, y, z)
11 [x, y, z]
12 end
13 big_arguments :bar
14 end
15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"]

Here's another example. Lines 4, 5 and 6. They inform you about
nil things.

1 class Module
2 def find_nil(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 if args.include?(nil)
5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
6 end
7 org_method.call(*args, &block)
8 end
9 end
10 end
11 class Foo
12 def bar(x, y, z)
13 end
14 find_nil :bar
15 end
16 Foo.new.bar("a", "b", "c") # ===>
17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"].
18 Foo.new.bar("a", "b", "c") # ===>

I call "typed", "just_wrap", "big_arguments" and "find_nil":
monitor-functions. I don't know exactly how this term got into
my head, but it does sound good: monitor-functions. It's
definitely better than wrap-method-functions. (You can build
non-monitor-functions as well. But that's really stupid:
monitor-and-non-monitor-functions.)

Meanwhile, I played with a couple of monitor-functions:
debugging, logging, synchronization, statistics, benchmarking,
roles (like on WebSphere). Ideas? It's easy to create them. Try
it. Let me know.

Forget about the implementation of "wrap_method". It's just
sitting there, waiting to be used to implement a
monitor-function. It's easy to implement a monitor-function.
And it's very, very easy to use it. Those where my goals.

Oh, by the way, if such a monitor-function is kind of
meta-programming (it's a buzz-word, I know, but it is, isn't
it?), how would you call "wrap_method"? Meta-meta-programming?

It was just an idea. Just wanted to tell you. Couldn't sleep.
Feel much better now. Maybe I can sleep...

Thanks for listening.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

PS: Sorry for this rather lengthy post. It just got a bit
lengthier than I planned. It just happened. No control.

----------------------------------------------------------------

class Module

# With this, we can create monitoring functions.
# It might not be clearly readable,
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
# It should be part of Ruby itself, anyway... :)

def wrap_method(method_name, *args1, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1

prefix = "_wrap_method_#{@_wrap_method_count_}"

module_eval <<-EOF
alias :#{prefix}_org :#{method_name} # Store the original method for later use.

define_method:)#{prefix}_args) {args1} # Store the arguments of the call to Module#wrap_method. (Not used.)
define_method:)#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.

def #{method_name}(*args2, &block2)
#{prefix}_block.call(method:)#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
end
EOF
end

end

----------------------------------------------------------------
 
E

Erik Veenstra

I forgot to show you the implementation of this "typed".

Well, here it is...

gegroet,
Erik V. - http://www.erikveen.dds.nl/

----------------------------------------------------------------

# IMPLEMENTATION

class Module
def typed(method_name, *types)
wrap_method(method_name) do |org_method, args, block|
args.each_with_index do |args, n|
[types[n]].flatten.each do |typ|
if typ.kind_of?(Module)
unless arg.kind_of?(typ)
raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
end
elsif typ.kind_of?(Symbol)
unless arg.respond_to?(typ)
raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
end
else
raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
end
end
end

org_method.call(*args, &block)
end
end
end

----------------------------------------------------------------

# TEST SCRIPT

class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :gsub and :to_s
:good
end

typed :bar, Numeric, String, [:gsub, :to_s]
end

def test(*args)
begin
puts "#{args.inspect} : OK : #{Foo.new.bar(*args).inspect}"
rescue Exception => e
puts "#{args.inspect} : NOK : #{e.message}"
end
end

puts
puts File.open(__FILE__){|f| f.readlines}.select{|x| x =~ /^\s*typed\b/}.join("\n")
puts

test(7)
test(7, 8, 9)
test(7, 8, "9")
test(7, "8", 9)
test(7, "8", "9")

----------------------------------------------------------------
 
M

m4dc4p

Slick!

Erik said:
I had a discussion with a friend. A Java guy. He wants the
arguments of a method call being checked. "I want the first one
to be an Integer. And the second one is a String. Period." No
discussion. I explained our duck-typing paradigm. He's not
convinced. He thinks Java. So, he gets Java.

Lets check the types of the arguments of a method call!

(This post is not about type checking at all. It's about how to
implement such a type checker. Or, more general, it's about
monitoring-functions.)

I wanted to do this with a nice and clean implementation, with
the real magic pushed down to a place I would never come again
("write once, read never"). I wanted something like this (focus
on line 7):

1 class Foo
2 def bar(x, y, z)
3 # x should be Numeric
4 # y should be a String
5 # z should respond to :to_s
6 end
7 typed :bar, Numeric, String, :to_s # !!!!!
8 end

Focus on line 7, once again. Make it three times. It's all
about line 7.

That was good enough for him. "But you can't do this. You
simply can't. That's magic." I laughed at him, turned around
and did it...

That's where this story is all about...

First, I'll give you a piece of code which doesn't do anything,
except that it seems to wrap the original method in another
method (focus on line 12):

1 class Module
2 def just_wrap(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 org_method.call(*args, &block)
5 end
6 end
7 end
8 class Foo
9 def bar(x, y, z)
10 p [x, y, z]
11 end
12 just_wrap :bar # !!!!!
13 end
14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"]

You can find the implementation of wrap_method below. This
thread is all about that very one method. It's the big trick.
You don't need to understand its implementation. Knowing how to
use it is good enough.

Line 3 retrieves the original method and yields the given block
with this method, as well as with its arguments and block. Not
*args, not &block. Just args and block. Blocks don't get
blocks, you know. (Although it's introduced in Ruby 1.9.)

Within the given block, we can do whatever we want to. That's
where the real stuff goes.

But, someday, we have to call the original method with the
original parameters and the original block. That's what we do
on line 4.

That's about it. That's the whole story. There's nothing more
to say.

Except for an example or two...

Here's a simple example. It "upcases" every argument. It must
be silly to "upcase" every argument like this, but we'll do it
anyway. Introducing line 4:

1 class Module
2 def big_arguments(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 args = args.collect{|x| x.to_s.upcase}
5 org_method.call(*args, &block)
6 end
7 end
8 end
9 class Foo
10 def bar(x, y, z)
11 [x, y, z]
12 end
13 big_arguments :bar
14 end
15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"]

Here's another example. Lines 4, 5 and 6. They inform you about
nil things.

1 class Module
2 def find_nil(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 if args.include?(nil)
5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
6 end
7 org_method.call(*args, &block)
8 end
9 end
10 end
11 class Foo
12 def bar(x, y, z)
13 end
14 find_nil :bar
15 end
16 Foo.new.bar("a", "b", "c") # ===>
17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"].
18 Foo.new.bar("a", "b", "c") # ===>

I call "typed", "just_wrap", "big_arguments" and "find_nil":
monitor-functions. I don't know exactly how this term got into
my head, but it does sound good: monitor-functions. It's
definitely better than wrap-method-functions. (You can build
non-monitor-functions as well. But that's really stupid:
monitor-and-non-monitor-functions.)

Meanwhile, I played with a couple of monitor-functions:
debugging, logging, synchronization, statistics, benchmarking,
roles (like on WebSphere). Ideas? It's easy to create them. Try
it. Let me know.

Forget about the implementation of "wrap_method". It's just
sitting there, waiting to be used to implement a
monitor-function. It's easy to implement a monitor-function.
And it's very, very easy to use it. Those where my goals.

Oh, by the way, if such a monitor-function is kind of
meta-programming (it's a buzz-word, I know, but it is, isn't
it?), how would you call "wrap_method"? Meta-meta-programming?

It was just an idea. Just wanted to tell you. Couldn't sleep.
Feel much better now. Maybe I can sleep...

Thanks for listening.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

PS: Sorry for this rather lengthy post. It just got a bit
lengthier than I planned. It just happened. No control.

----------------------------------------------------------------

class Module

# With this, we can create monitoring functions.
# It might not be clearly readable,
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
# It should be part of Ruby itself, anyway... :)

def wrap_method(method_name, *args1, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1

prefix = "_wrap_method_#{@_wrap_method_count_}"

module_eval <<-EOF
alias :#{prefix}_org :#{method_name} # Store the original method for later use.

define_method:)#{prefix}_args) {args1} # Store the arguments of the call to Module#wrap_method. (Not used.)
define_method:)#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.

def #{method_name}(*args2, &block2)
#{prefix}_block.call(method:)#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
end
EOF
end

end

----------------------------------------------------------------
 
E

Eric Hodel

I had a discussion with a friend. A Java guy. He wants the
arguments of a method call being checked. "I want the first one
to be an Integer. And the second one is a String. Period." No
discussion. I explained our duck-typing paradigm. He's not
convinced. He thinks Java. So, he gets Java.

Lets check the types of the arguments of a method call!

(This post is not about type checking at all. It's about how to
implement such a type checker. Or, more general, it's about
monitoring-functions.)

I wanted to do this with a nice and clean implementation, with
the real magic pushed down to a place I would never come again
("write once, read never"). I wanted something like this (focus
on line 7):

1 class Foo
2 def bar(x, y, z)
3 # x should be Numeric
4 # y should be a String
5 # z should respond to :to_s
6 end
7 typed :bar, Numeric, String, :to_s # !!!!!
8 end

Focus on line 7, once again. Make it three times. It's all
about line 7.

That was good enough for him. "But you can't do this. You
simply can't. That's magic." I laughed at him, turned around
and did it...

For bonus points, record stats for every time your assertion fails
and you generate a "type error" compared with every time it does
nothing. Hopefully you can show your coworker how useless the code
really is.
 
L

Lionel Thiry

http://www.rcrchive.net/rcr/show/321

Erik Veenstra a écrit :
1 class Module
2 def just_wrap(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 org_method.call(*args, &block)
5 end
6 end
7 end
8 class Foo
9 def bar(x, y, z)
10 p [x, y, z]
11 end
12 just_wrap :bar # !!!!!
13 end
14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"]

class Foo
def bar(x, y, z)
p [x, y, z]
end
end

cut JustWrap < Foo
def bar
super
end
end
1 class Module
2 def big_arguments(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 args = args.collect{|x| x.to_s.upcase}
5 org_method.call(*args, &block)
6 end
7 end
8 end
9 class Foo
10 def bar(x, y, z)
11 [x, y, z]
12 end
13 big_arguments :bar
14 end
15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"]

class Foo
def bar(x, y, z)
p [x, y, z]
end
end

cut BigArguments < Foo
def bar(*args)
super(*args.collect{|x| x.to_s.upcase})
end
end
1 class Module
2 def find_nil(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 if args.include?(nil)
5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
6 end
7 org_method.call(*args, &block)
8 end
9 end
10 end
11 class Foo
12 def bar(x, y, z)
13 end
14 find_nil :bar
15 end
16 Foo.new.bar("a", "b", "c") # ===>
17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"].
18 Foo.new.bar("a", "b", "c") # ===>

class Foo
def bar(x, y, z)
end
end

cut FindNil < Foo
def bar(*args)
if args.include?(nil)
$stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
end
super(*args)
end
end
 
L

Lou Vanek

you should consider ara's 'traits' library, too, for Java Joe.
http://codeforpeople.com/lib/ruby/traits/


Erik said:
I had a discussion with a friend. A Java guy. He wants the
arguments of a method call being checked. "I want the first one
to be an Integer. And the second one is a String. Period." No
discussion. I explained our duck-typing paradigm. He's not
convinced. He thinks Java. So, he gets Java.

Lets check the types of the arguments of a method call!

(This post is not about type checking at all. It's about how to
implement such a type checker. Or, more general, it's about
monitoring-functions.)

I wanted to do this with a nice and clean implementation, with
the real magic pushed down to a place I would never come again
("write once, read never"). I wanted something like this (focus
on line 7):

1 class Foo
2 def bar(x, y, z)
3 # x should be Numeric
4 # y should be a String
5 # z should respond to :to_s
6 end
7 typed :bar, Numeric, String, :to_s # !!!!!
8 end

Focus on line 7, once again. Make it three times. It's all
about line 7.

That was good enough for him. "But you can't do this. You
simply can't. That's magic." I laughed at him, turned around
and did it...

That's where this story is all about...

First, I'll give you a piece of code which doesn't do anything,
except that it seems to wrap the original method in another
method (focus on line 12):

1 class Module
2 def just_wrap(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 org_method.call(*args, &block)
5 end
6 end
7 end
8 class Foo
9 def bar(x, y, z)
10 p [x, y, z]
11 end
12 just_wrap :bar # !!!!!
13 end
14 Foo.new.bar("a", "b", "c") # ===> ["a", "b", "c"]

You can find the implementation of wrap_method below. This
thread is all about that very one method. It's the big trick.
You don't need to understand its implementation. Knowing how to
use it is good enough.

Line 3 retrieves the original method and yields the given block
with this method, as well as with its arguments and block. Not
*args, not &block. Just args and block. Blocks don't get
blocks, you know. (Although it's introduced in Ruby 1.9.)

Within the given block, we can do whatever we want to. That's
where the real stuff goes.

But, someday, we have to call the original method with the
original parameters and the original block. That's what we do
on line 4.

That's about it. That's the whole story. There's nothing more
to say.

Except for an example or two...

Here's a simple example. It "upcases" every argument. It must
be silly to "upcase" every argument like this, but we'll do it
anyway. Introducing line 4:

1 class Module
2 def big_arguments(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 args = args.collect{|x| x.to_s.upcase}
5 org_method.call(*args, &block)
6 end
7 end
8 end
9 class Foo
10 def bar(x, y, z)
11 [x, y, z]
12 end
13 big_arguments :bar
14 end
15 Foo.new.bar("a", "b", "c") # ===> ["A", "B", "C"]

Here's another example. Lines 4, 5 and 6. They inform you about
nil things.

1 class Module
2 def find_nil(method_name)
3 wrap_method(method_name) do |org_method, args, block|
4 if args.include?(nil)
5 $stderr.puts "Found a nil when called from #{caller[1..-1].inspect}."
6 end
7 org_method.call(*args, &block)
8 end
9 end
10 end
11 class Foo
12 def bar(x, y, z)
13 end
14 find_nil :bar
15 end
16 Foo.new.bar("a", "b", "c") # ===>
17 Foo.new.bar("a", "b", nil) # ===> Found a nil when called from from ["test.rb:17"].
18 Foo.new.bar("a", "b", "c") # ===>

I call "typed", "just_wrap", "big_arguments" and "find_nil":
monitor-functions. I don't know exactly how this term got into
my head, but it does sound good: monitor-functions. It's
definitely better than wrap-method-functions. (You can build
non-monitor-functions as well. But that's really stupid:
monitor-and-non-monitor-functions.)

Meanwhile, I played with a couple of monitor-functions:
debugging, logging, synchronization, statistics, benchmarking,
roles (like on WebSphere). Ideas? It's easy to create them. Try
it. Let me know.

Forget about the implementation of "wrap_method". It's just
sitting there, waiting to be used to implement a
monitor-function. It's easy to implement a monitor-function.
And it's very, very easy to use it. Those where my goals.

Oh, by the way, if such a monitor-function is kind of
meta-programming (it's a buzz-word, I know, but it is, isn't
it?), how would you call "wrap_method"? Meta-meta-programming?

It was just an idea. Just wanted to tell you. Couldn't sleep.
Feel much better now. Maybe I can sleep...

Thanks for listening.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

PS: Sorry for this rather lengthy post. It just got a bit
lengthier than I planned. It just happened. No control.

----------------------------------------------------------------

class Module

# With this, we can create monitoring functions.
# It might not be clearly readable,
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
# It should be part of Ruby itself, anyway... :)

def wrap_method(method_name, *args1, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1

prefix = "_wrap_method_#{@_wrap_method_count_}"

module_eval <<-EOF
alias :#{prefix}_org :#{method_name} # Store the original method for later use.

define_method:)#{prefix}_args) {args1} # Store the arguments of the call to Module#wrap_method. (Not used.)
define_method:)#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.

def #{method_name}(*args2, &block2)
#{prefix}_block.call(method:)#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
end
EOF
end

end

----------------------------------------------------------------
 
T

Trans

So I have to replace every Foo.new to JustWrap.new, just to
activate the debugging? Kidding?

No you do not. A cut is a _transparent_ class. You would still use
Foo.new.

T.
 
M

Maurice Codik

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

Yet another suggestion-- I wrote a little library that lets you define
simple contracts for function inputs... useful if you dont want to repeat
the same duck-type assertions over and over again.

here's an example of how it works:

require "contracts"
class TestContracts
extend Contracts

define_data :writable =3D> lambda {|x| x.respond_to?("write") and
x.respond_to?("closed?") and not x.closed? },
:positive =3D> lambda {|x| x >=3D 0 }

contract :hello, [:positive, :string, :writable]

def hello(n, s, f)
n.times { f.write "hello #{s}!\n" }
end
end

tc =3D TestContracts.new
tc.hello(2, "world", $stdout)
# -> hello world!
# -> hello world!

# tc.hello(2, 3, $stdout)
# -> test-contracts.rb:22: argument 2 of method 'hello' must satisfy
the 'string' contract (Contracts::ContractViolation)

You can download it at: : http://mauricecodik.com/projects/ruby/contracts.r=
b

Maurice

No you do not. A cut is a _transparent_ class. You would still use
Foo.new.

T.

------=_Part_14829_16318285.1139360026835--
 
T

Trans

But this is an example of why you wouldn't really want this
functionality in Ruby right? We all know there are times we need to
contrain arguments, but that should be exception, not the norm. Hence
the beauty of ducktyping.

T.
 
V

vidar.hokstad

Trans said:
But this is an example of why you wouldn't really want this
functionality in Ruby right? We all know there are times we need to
contrain arguments, but that should be exception, not the norm. Hence
the beauty of ducktyping.

That's somewhat short-sighted. Any method in a language like Ruby will
either not be fully defined (that is, it will fail for a subset of
possible inputs), or will be full of explicit type checking (kind_of?
etc). For the most part, the former is chosen. For the most part it
"works", sort of.

But it works because most of the time people have reasonable
expectations of what a method will expect, or you read the
documentation (and it is up to date enough) and you test.

However this is nothing more than making Ruby enforce a contract: If
your method DOES need #to_s to be present for one of the arguments for
the method to be well defined, I for one would prefer to find out as
early as possible rather than have it suddenly break on me because
things just happens to work without it "most of the time".

Properly written preconditions both reduces the test cases - the set of
different classes of input can be constrained significantly - and helps
document the code _and_ ensure that this documentation is likely to
stay in sync with the code, unlike documentation that has no effect on
your tests.

There's nothing contradictory between this method and ducktyping -
ducktyping is about not relying on name tags but about actual features
(that is, it's having the #to_s method that is important, not having
been labeled as implementing a hypotetical "CanConvertToString"
interface), and this method can be used to check for that.

It can of course also be abused to make type checks that are far too
generic, and I can to a certain extent agree with you that using it to
constrain arguments to a specific class may be undesirable most of the
time (... after having seen full well how annoying badly done type
checking is from dealing with REXML...)

Consider it inline documentation and an additional testing and
debugging tool - if runtime performance is affected too much you could
always add a switch to make it only actually wrap the methods if $DEBUG
is set and otherwise leave them alone.

Vidar
 
E

Erik Veenstra

Well written. Thanks. I'll forward it to Java Joe. :)

(Although my original post wasn't about type checking, or about
duck-type checking. It was about how to implement such a
wrapper...)

Tanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
E

Erik Veenstra

Did I mention that it is possible to double-wrap a method with
two or more monitor-functions? The order in which they are
executed is down-up:

class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
end
typed :bar, Numeric, String, [:to_s, :gsub]
log_args :bar
end

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
D

Daniel Nugent

So, just for clarifications sake: Both the presented wrapping method
and the cut library used earlier are implementations (generic uses?)
of Aspect Oriented Programming, right?

Nothing *more* than that going on, correct?

Vidar, I think the question there is: Should I rely on a type/method
check to ensure that I don't get bad parameters or should I just write
some tests to make sure that the code in question fails in a sensible
way when those expectations aren't met. Those would be edge cases
after all, and you'd have to write the tests for them anyway.

To me, it seems to be unDRY...

Except if you were using it to enhance performance through typing
related optimizations.

Did I mention that it is possible to double-wrap a method with
two or more monitor-functions? The order in which they are
executed is down-up:

class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
end
typed :bar, Numeric, String, [:to_s, :gsub]
log_args :bar
end

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
V

vidar.hokstad

Daniel said:
Vidar, I think the question there is: Should I rely on a type/method
check to ensure that I don't get bad parameters or should I just write
some tests to make sure that the code in question fails in a sensible
way when those expectations aren't met. Those would be edge cases
after all, and you'd have to write the tests for them anyway.

To me, it seems to be unDRY...

To me the issue is to avoid surprises. If your function will need a
specific method every few million times it is executed, or on specific
dates, or when a specific race condition occurs, or when processing
specific user input, it might require a lot of work for a user of your
code to verify that their application works as expected through testing
unless they know exactly what they need to test for.

More importantly: Unless _they_ verify these preconditions in their
test cases they will have to handle whatever you consider a "sensible
way of failing". If your failure mode doesn't match their expectations,
it might take a lot of work to set verify that there is actually a
problem, and it can easily slip through.

This is a pragmatic way of ensuring the least possibility of surprise,
by forcing a failure as early as possible. The other alternative is to
document these cases painstakingly and depend on the users of your code
to test for them. But why put your users through that pain if you have
an easy way of trapping the error early on that at the same time serves
as explicit documentation of what your code expects?

I am not saying it's always what you want, or that you'll always see
benefits from it. But there are certainly cases where the potential
problems caused by a failure are severe enough that it is better to
cause a failure early on. If I am going to do batch database updates on
a millions of rows for instance, I'd much prefer to find corner cases
right away during testing, than risk having the code fail with a
NoMethodError two days into a production run because I hit a bizarre
corner case.

It's not always a case of "just writing some unit tests" unless you
first spend ages analysing the code you are calling to verify exactly
how to trigger all corner cases.

Simplifying unit tests is exactly one of the compelling uses for this -
the earlier your methods explicitly check for and fail if preconditions
are not met, the smaller the input set you need to test is likely to
be.

It is even more compelling because it can be easily adapted so that it
is easy to turn off for production code if performance becomes an
issue: As I suggested, you could easily make the wrapper do nothing
unless $DEBUG is defined, for instance.

Vidar
 
A

Austin Ziegler

To me the issue is to avoid surprises. If your function will need a
specific method every few million times it is executed, or on specific
dates, or when a specific race condition occurs, or when processing
specific user input, it might require a lot of work for a user of your
code to verify that their application works as expected through
testing unless they know exactly what they need to test for.

That's what documentation is for.
More importantly: Unless _they_ verify these preconditions in their
test cases they will have to handle whatever you consider a "sensible
way of failing". If your failure mode doesn't match their
expectations, it might take a lot of work to set verify that there is
actually a problem, and it can easily slip through.

That's what documentation is for.
This is a pragmatic way of ensuring the least possibility of surprise,
by forcing a failure as early as possible. The other alternative is to
document these cases painstakingly and depend on the users of your
code to test for them. But why put your users through that pain if you
have an easy way of trapping the error early on that at the same time
serves as explicit documentation of what your code expects?

Except that contract enforcement is *expensive*, and most contracts are
much more difficult to express than can be expressed in the way that
people who are (foolishly) comforted by static typing expect.

-austin
 
V

vidar.hokstad

Erik said:
(Although my original post wasn't about type checking, or about
duck-type checking. It was about how to implement such a
wrapper...)

The generic wrapper is absolutely interesting :) It was the typing part
that triggered my interest, though, because I do prefer stricter type
checking myself.

_But_ at the same time I like the ability to selectively use it where
it matters, such as to document and enforce genuine constraints and
trigger errors as early as possible (as a consequence, I don't see that
much value in checking for a specific type, but I did like the ease of
checking for support for a specific method)

To me "ducktyping" doesn't preclude fairly strict type checking.
Haskell, for instance, for all intents and purposes provides most of
the same flexibility in terms of typing from the programmers point of
view, but still enforces typing strictly by inferring which types would
satisfy the requirements of a specific piece of code. Barring that kind
of support in the Ruby interpreter, being able to selectively and
easily wrap type check around code where the requirements are
non-obvious and hard to check is quite useful.

I like the possibilities this has for aspect oriented programming for
things like debugging and testing too - by injecting wrappers to
manipulate or check parts of the interactions in the tested code (I did
read most of RCR 321 that someone else mentioned too, but the appeal of
your suggestion is the size/simplicity of the implementation)

Vidar
 
E

Erik Veenstra

Oops! A little typo...

args.each_with_index do |args, n|

....should be:

args.each_with_index do |arg, n|
 
V

vidar.hokstad

Austin said:
That's what documentation is for.

People don't read documentation thoroughly enough.

And even if they do, that still leaves them with a major testing
headache if artificially creating the corner cases that triggers
specific behavior from your code is hard to do.

Relying on the documentation for something that is easy to check is a
cop out. _Especially_ when it is trivial to turn that check off for
production code and remove all cost.
Except that contract enforcement is *expensive*, and most contracts are
much more difficult to express than can be expressed in the way that
people who are (foolishly) comforted by static typing expect.

Many contracts are expensive yes and many aren't - I have lots of code
that depend on a single or a small set of methods to be available on an
object that is passed in. Checking it is easy. Even so, I've pointed
out that one of the things I liked about the wrapping is that it's
trivially easy to make it optional at runtime at no cost.

The wrapping approach has the advantages that:

- The checks can be put in a separate source file and only included in
the application when you want them (or you can keep cheap checks in
critical parts of the code, and keep more expensive checks or checks on
less critical code separate and not use it in production code)
- The checks can be trivially disabled in a such a way that there is
_no_ cost when the methods are executed (by simply switching the
wrapping off)
- It's trivial to expand the checks without making the core code more
complex
- It doubles as documentation that you know will be kept up to date
because things will break if it isn't. If there is one thing I NEVER
trust, it is documentation that isn't executable - it invariably gets
out of date, blatantly wrong, and outright dangerous to depend on.
- They assist in minimising the effort of writing test code for
clients of your code, by minimising the number of potential code paths.

This has nothing to do with being "foolishly comforted by static
typing" but about 1) facilitating testing and debugging, 2) avoiding
late surprises, by failing early where possible, both of which are
good, sound practices regardless of whether you use static or dynamic
typing.

Vidar
 
M

Maurice Codik

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

I dont really understand why the reaction to this is so negative-- design b=
y
contract can be a very useful tool (even if the code presented in thir
thread is a very limited implementation of DBC).

Methods usually spend a few lines of code validating their arguments,
especially if they are intended to be used by external clients. Your
documentation should definately explain what type of arguments your methods
expect-- and your code should make sure that its users are providing the
correct kind of arguments. You want to error ASAP if you are given a bad
argument. From a user's perspective, a "bad argument" error thrown at the
library boundary is much easier to debug than a strange NoMethodError throw=
n
deep inside your library.

Libraries like these just make argument validation a little more DRY (ex,
define your contract once, apply it to many methods). It is not just static
typing-- the library I wrote, for example, lets you provide Procs to check
contracts, so you can test more dynamic/runtime properties such as "this
database connection is open."

Performance cost? If you are going to be validating the parameters to your
methods anyway (which you should), there is very little additional overhead=
 

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,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top