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 => 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
···
On 2/7/06, Trans <transfire@gmail.com> wrote:
> 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.
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/
Daniel Nugent wrote:
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.
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
···
On 08/02/06, vidar.hokstad@gmail.com <vidar.hokstad@gmail.com> wrote:
--
Austin Ziegler * halostatue@gmail.com
* Alternate: austin@halostatue.ca
My beef with the specific implementation in this thread as it applies
to Design By Contract is that you're issuing these wrapper methods
individually.
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.
···
On 2/8/06, Mauricio Fernandez <mfp@acm.org> wrote:
On Thu, Feb 09, 2006 at 01:44:46AM +0900, James Edward Gray II wrote:
> On Feb 8, 2006, at 7:48 AM, Erik Veenstra wrote:
>
> >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?
>
> Higher-order function - Wikipedia
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
--
-Dan Nugent
Erik Veenstra wrote:
(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
Austin Ziegler wrote:
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
I dont really understand why the reaction to this is so negative-- design by
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 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.
Maurice
···
On 2/8/06, Austin Ziegler <halostatue@gmail.com> wrote:
On 08/02/06, vidar.hokstad@gmail.com <vidar.hokstad@gmail.com> wrote:
> Daniel Nugent wrote:
>> 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.
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 * halostatue@gmail.com
* Alternate: austin@halostatue.ca
class Example
def one; ... end
def two; ... end
%w{one two}.each do |meth|
apply_contract meth, ...
end
end
James Edward Gray II
···
On Feb 8, 2006, at 1:11 PM, Daniel Nugent wrote:
My beef with the specific implementation in this thread as it applies
to Design By Contract is that you're issuing these wrapper methods
individually.
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.
Like this?
gegroet,
Erik V. - http://www.erikveen.dds.nl/
···
----------------------------------------------------------------
require "ev/typed"
class Module
def my_conditions(*method_names)
method_names.each do |method_name|
wrap_method(method_name) do |org_method, args, block|
# pre_conditions
org_method.call(*args, &block)
# post_conditions
end
end
end
end
class Foo
# A log of method definitions
my_conditions :foo, :bar, :and, :friends
end
----------------------------------------------------------------
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)
<quote src="Vidar">
... is the size/simplicity of the implementation
<quote>
At least *you* got my point. Thanks.
gegroet,
Erik V. - http://www.erikveen.dds.nl/