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
----------------------------------------------------------------
> -----Original Message-----
> From: Erik Veenstra [mailto:p...@erikveen.dds.nl]
> Sent: Tuesday, February 07, 2006 3:18 PM
> To: ruby-talk ML
> Subject: Meta-Meta-Programming
>
>
>
> 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!
Let's use the strongtyping package on the RAA!
Dan
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")
----------------------------------------------------------------
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.
--
Eric Hodel - drb...@segment7.net - http://segment7.net
This implementation is HODEL-HASH-9600 compliant
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
--
Lionel Thiry
Personal web site: http://users.skynet.be/lthiry/
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.
here's an example of how it works:
require "contracts"
class TestContracts
extend Contracts
define_data :writable => lambda {|x| x.respond_to?("write") and
x.respond_to?("closed?") and not x.closed? },
:positive => lambda {|x| x >= 0 }
contract :hello, [:positive, :string, :writable]
def hello(n, s, f)
n.times { f.write "hello #{s}!\n" }
end
end
tc = 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.rb
Maurice
T.
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
(Although my original post wasn't about type checking, or about
duck-type checking. It was about how to implement such a
wrapper...)
Tanks.
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
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.
--
-Dan Nugent
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
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
--
Austin Ziegler * halos...@gmail.com
* Alternate: aus...@halostatue.ca
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
args.each_with_index do |args, n|
...should be:
args.each_with_index do |arg, n|
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
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 thrown
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
Now, we have this Module#wrap_method for wrapping instance
methods. But what about wrapping module methods, like
Module#wrap_module_method?
We are going to take this dangerous type checking thing to the
next level. Just as an example. JUST AS AN EXAMPLE!!!
Imagine, we want to move the types to be positioned before the
method instead of after the method. Just for better
readability:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
end
... instead of:
class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
typed :bar, Numeric, String, [:to_s, :gsub]
end
We can do this by storing the types in def_types and
overwriting Foo::method_added. But what about the old
functionality in Foo::method_added? Sure, alias to another
method and than use this alias. That's the common way to work
around this problem. (I've never liked it...) But, again, we
can use "wrap_method" to add the new functionality to the
original method. Introducing "wrap_module_method":
def def_types(*types)
wrap_module_method(:method_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
Do you see that this "wrap_module_method" looks like
"wrap_method"? They should look the same. They are brother and
sister.
Since we do a "wrap_module_method" in "method_added" and
"wrap_module_method" does a "wrap_method" and "wrap_method"
adds a method and thus does a "method_added", the wrapped
"method_added" gets called over and over again. That's why I
introduced this loop-avoiding-system. You only have to add this
mechanism when wrapping Module#method_added, not for other
module methods.
Once again, it should be possible to wrap the wrapper:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def_stat "/tmp/stats.log"
def bar1(x, y, z)
end
def bar2(x, y, z) # bar2 is neither logged, nor checked.
end
end
See below for a full implementation of both wrapping methods
and of both type checking methods.
(Once again, it's not about this type checking, or duck-type
checking: it's all about "wrap_method" and
"wrap_module_method".)
gegroet,
Erik V. - http://www.erikveen.dds.nl/
----------------------------------------------------------------
class Module
# Meta-Meta-Programming
# 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, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1
prefix = "_wrap_method_#{@_wrap_method_count_}"
module_eval <<-EOF
if instance_methods.include?(:#{method_name}.to_s)
alias :#{prefix}_org :#{method_name} # Store the original method for later use.
end
define_method(:#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.
def #{method_name}(*args2, &block2)
if respond_to?(:#{prefix}_org)
#{prefix}_block.call(method(:#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
else
#{prefix}_block.call(nil, args2, block2)
end
end
EOF
end
def wrap_module_method(method_name, &block1)
class << self
self
end.module_eval do
wrap_method(method_name) do |org_method, args2, block2|
block1.call(org_method, args2, block2)
end
end
end
end
----------------------------------------------------------------
class Module
# Type checking.
# Or duck-type checking.
# Example:
# class Foo
# def_types String, Numeric, [:to_s, :gsub]
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# end
def def_types(*types)
wrap_module_method(:method_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
# Example:
# class Foo
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# typed :bar, String, Numeric, [:to_s, :gsub]
# end
def typed(method_name, *types)
wrap_method(method_name) do |org_method, args, block|
args.each_with_index do |arg, n|
<quote src="Vidar">
... is the size/simplicity of the implementation
<quote>
At least *you* got my point. Thanks.
A bit more readable:
"def_types" wraps "method_added."
From now on, every time "method_added" is called, it calls
"typed" (if we remove "if types"), which calls "wrap_method,"
which adds methods and thus triggers "method_added." And so on.
This "types = nil", also applies the wrapping to *only* the
next method (only to Foo#bar1 and not to Foo#bar2).
> Did I mention that it is possible to double-wrap a method with
> two or more monitor-functions?
How is the term "monitor-functions" you have used in this thread
different from the standard "higher-order functions," if you don't
mind my asking?
http://en.wikipedia.org/wiki/Higher-order_functions
James Edward Gray II
Erik's "monitor-functions" would be a subset of (the more general)
higher-order functions (for a relaxed definition, accounting for the
fact that neither the input nor the output are actually functions, but
the symbols given as input map to methods, and the "output" is the
side-effect of changing a method definition).
The "type-checking monitors" would be noted as
env(#f=f1) x meth-name x arg-type-list -> env(#f=f2)
========= ============= =========
'world state' type-checking info new environment where
where #f is #f checks the args before
defined as f1 doing f1
(f1 would be the AST)
--
Mauricio Fernandez
I once used a similar technique to wrap all calls to the logger. Then,
if the application threw an exception, the crash report would include
all of the recent log entries (even ones that were marked DEBUG and
hence not saved to disk).
As an aside, this really highlights some of the cultural differences
between Ruby and other developers regarding what defines "simplicity".
You only need to write one line to wrap. But, if you want to know
what's going on, you get:
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
Rubyists usually define this as simple.
Other developers don't:
http://www.artima.com/intv/simplexity.html
Anders Hejlsberg: Let me first talk a little bit about how I view
simplicity in general. No one ever argues that simplicity isn't good,
but people define simplicity in a variety of ways. There's one kind of
simplicity that I like to call simplexity. When you take something
incredibly complex and try to wrap it in something simpler, you often
just shroud the complexity. You don't actually design a truly simple
system. And in some ways you make it even more complex, because now the
user has to understand what was omitted that they might sometimes need.
That's simplexity
I think this highlights the key cultural difference between Ruby &
Python, and shows why Rubyists love all the meta programming, class
methods, DSL (like Rails' has_one etc.), and the like, while
Pythonistas avoid them.
A monitor-function isn't a higher-order function, since it
takes the name of a method, not the method itself. Nor does it
_return_ a function. Well, it does _create_ a new function as a
side effect, but that's not part of the definition of
higher-order functions. Higher-order functions are used in
Lisp. In functional programming (that's what you try to do in
Lisp) side-effects are considered bad. In OO (Ruby) it's
considered common. (Although monitor-functions are not
OO'ish...)
Module#wrap_method, used in the implementation of a
monitor-function, is a higher-order function: It takes a block.
So, the relation between monitor-functions and higher-order
functions that does exist, is invisible to the user of a
monitor-function.
A more preferential solution would involve setting up a list of
functions that need a contract applied and then being able to, in one
fell swoop, ensure that they all are bound to the contract at once,
whether that be preconditions, postconditions, or ensurances about the
parameters passed. Maybe that'll require a couple of method calls,
but you'd still generally be doing it all at once.
I think, for me, the way we're talking about it here quacks too much
like static typing.
--
-Dan Nugent