[#98950] Strange behaviour of Strings in Range — Michael Neumann <mneumann@...>

Hi,

24 messages 2004/05/01

[#98975] Why no Proc##[]=() ? Why no Proc##replace() ? — Jean-Hugues ROBERT <jean_hugues_robert@...>

Hi,

15 messages 2004/05/01
[#98983] Re: Why no Proc##[]=() ? Why no Proc##replace() ? — Dan Doel <djd15@...> 2004/05/01

I'm not sure what your proposal means.

[#98997] Re: Why no Proc##[]=() ? Why no Proc##replace() ? — Jean-Hugues ROBERT <jean_hugues_robert@...> 2004/05/02

About class Proc; def []=(*args); self[*args] end end and the value of

[#98980] Ruby Newbie (ooh, that rhymes!) - When do I use do/end, when {}? — glenn_m_smith@... (Glenn)

Hello all

23 messages 2004/05/01

[#99105] What do you use Ruby for? — glenn_m_smith@... (Glenn)

OK, the more I read about Ruby (currently on page 29 of the FAQ,

25 messages 2004/05/03

[#99119] YAML vs. Marshal — dejaspam@... (Bill Atkins)

Is there any reason to use Marshal instead of YAML? Is there anything

21 messages 2004/05/03
[#99121] Re: YAML vs. Marshal — Joel VanderWerf <vjoel@...> 2004/05/03

Bill Atkins wrote:

[#99191] Re: YAML vs. Marshal — why the lucky stiff <ruby-talk@...> 2004/05/04

Joel VanderWerf wrote:

[#99192] Re: YAML vs. Marshal — Hal Fulton <hal9000@...> 2004/05/04

why the lucky stiff wrote:

[#99204] What so special about PostgreSQL and other RDBMS? — "Sarah Tanembaum" <sarah.tanembaum@...>

Beside its an opensource and supported by community, what's the fundamental

96 messages 2004/05/04

[#99270] Is Ruby Top 1 of Programming Languages that are Loved? — "Park Heesob" <phasis68@...>

Hi all,

66 messages 2004/05/05
[#99333] Re: [OT]Is Ruby Top 1 of Programming Languages that are Loved? — ptkwt@... (Phil Tomson) 2004/05/06

In article <83173408.0405051506.5db85fe6@posting.google.com>,

[#99358] Re: [OT]Is Ruby Top 1 of Programming Languages that are Loved? — Dick Davies <rasputnik@...> 2004/05/06

* Phil Tomson <ptkwt@aracnet.com> [0554 02:54]:

[#99378] Re: [OT]Is Ruby Top 1 of Programming Languages that are Loved? — "Ara.T.Howard" <ahoward@...> 2004/05/06

On Thu, 6 May 2004, Dick Davies wrote:

[#99326] RAA error - can't dup NilClass — Simon Strandgaard <neoneye@...>

When I try to enter my project page

15 messages 2004/05/06
[#99327] Re: RAA error - can't dup NilClass — "NAKAMURA, Hiroshi" <nahi@...> 2004/05/06

Hi,

[#99328] Re: RAA error - can't dup NilClass — "NAKAMURA, Hiroshi" <nahi@...> 2004/05/06

Hi, again,

[#99332] miniruby.exe & statically linked ruby.exe (Windows) — ptkwt@... (Phil Tomson)

I've been trying to build a statically linked ruby.exe on windows using

12 messages 2004/05/06

[#99399] DRb Connection Closed Error?!?!?!? — "Ken Hilton" <kenosis@...>

Greetings,

14 messages 2004/05/06

[#99438] What is Borges? — dejaspam@... (Bill Atkins)

Can someone please explain to me what Borges does? Its home page

41 messages 2004/05/07
[#99482] Re: What is Borges? — Dick Davies <rasputnik@...> 2004/05/07

* Bill Atkins <dejaspam@batkins.com> [0534 01:34]:

[#99530] Re: What is Borges? — Julian Fitzell <julian@...4.com> 2004/05/07

Dick Davies wrote:

[#99534] Re: What is Borges? — Carl Youngblood <carl@...> 2004/05/07

[#99527] Ruby Installer for Windows: use of Win32OLE bug causes crash — Jos Backus <jos@...>

This is with the latest Ruby Installer for Windows, 181-13-rc2.

11 messages 2004/05/07

[#99583] Ruby Installer for Windows 1.8.1-13 final — "Curt Hibbs" <curt@...>

The Ruby Installer 1.8.1-13 (final) for Windows has been released and

62 messages 2004/05/08
[#99643] Why Ruby? -- A Resource for Promoting Ruby — "Curt Hibbs" <curt@...> 2004/05/09

I'm pleased to announce the kickoff of RubyForge project called "Why Ruby?"

[#104934] Ruby Installer for Windows 1.8.2-14 Release Candidate — "Curt Hibbs" <curt@...> 2004/06/30

I have uploaded a release candidate for Ruby Installer that was built using

[#104952] **RC2** Ruby Installer for Windows 1.8.2-14 Release Candidate — "Curt Hibbs" <curt@...> 2004/07/01

The original release candidate that I posted earlier today did not include

[#105519] Re: [ANN] **RC2** Ruby Installer for Windows 1.8.2-14 Release Candidate — Lothar Scholz <mailinglists@...> 2004/07/07

Hello Curt,

[#105546] Re: [ANN] **RC2** Ruby Installer for Windows 1.8.2-14 Release Candidate — Hidetoshi NAGAI <nagai@...> 2004/07/08

Hi,

[#105550] Re: [ANN] **RC2** Ruby Installer for Windows 1.8.2-14 Release Candidate — nobu.nokada@... 2004/07/08

Hi,

[#99597] How to get the last 5 elements of an array? — Gavin Sinclair <gsinclair@...>

Hi,

20 messages 2004/05/08

[#99680] rubygarden homepage hacks — Simon Strandgaard <neoneye@...>

On daily basis the homepage at rubygarden is being edited.

19 messages 2004/05/09

[#99734] in search of a compelling reason to use ruby.... — Ryan Paul <segphault@...>

i'm a python programmer, and I have recently been hearing a lot about

28 messages 2004/05/10

[#99764] safe eval? — "Ara.T.Howard" <Ara.T.Howard@...>

20 messages 2004/05/10
[#99773] Re: safe eval? — Florian Gross <flgr@...> 2004/05/10

Ara.T.Howard wrote:

[#99834] Re: safe eval? — ts <decoux@...> 2004/05/11

>>>>> "F" == Florian Gross <flgr@ccan.de> writes:

[#99854] Proposal: Object#send(nil) -> self — Gavin Sinclair <gsinclair@...>

A quick one. I see some sense in Object#send accepting 'nil' as the

21 messages 2004/05/11

[#99879] Strange regexp behaviour in gsub — Kristof Bastiaensen <kristof@...>

Hi,

15 messages 2004/05/11

[#99945] Recommendations (Ruby making my head swim) — Mike Rhodes <rhodes553@...>

I recently bought "Programming Ruby" and set out to learn the language

13 messages 2004/05/12

[#99966] Major Addition Bug? — "Sean O'Dell" <sean@...>

Doing this:

57 messages 2004/05/12
[#99967] Re: Major Addition Bug? — ts <decoux@...> 2004/05/12

>>>>> "S" == Sean O'Dell <sean@celsoft.com> writes:

[#99970] Re: Major Addition Bug? — "Sean O'Dell" <sean@...> 2004/05/12

On Wednesday 12 May 2004 09:53, ts wrote:

[#100032] are there any ruby IDEs? — Ryan Paul <segphault@...>

I use vim most of the time, but i'm curious if there are any

35 messages 2004/05/12

[#100093] How to make combinations of an array to produce all possible expressions? — Erik Terpstra <erik@...>

I have an array 'conds', which contains some sub-expressions for an

11 messages 2004/05/13

[#100136] Ruby's Best -- Please Help — "Curt Hibbs" <curt@...>

On the "Why Ruby?" project wiki (http://whyruby.rubyforge.org/), I am trying

23 messages 2004/05/13
[#100284] Please revisit Ruby's Best and vote for additions — "Curt Hibbs" <curt@...> 2004/05/14

If you were an early visitor to:

[#100137] First Presentation Posted to Why Ruby! — "Curt Hibbs" <curt@...>

Assaph Mehr just posted the first presentation to Why Ruby

22 messages 2004/05/13
[#100172] Presenting a more unified front (Ruby webring?) — ptkwt@... (Phil Tomson) 2004/05/13

In article <EAENKKNOJPMNCDMLDOMLKEHHEFAA.curt@hibbs.com>,

[#100182] C++ Exception compatibility idea — Asfand Yar Qazi <im_not_giving_it_here@..._hate_spam.com>

Hi,

26 messages 2004/05/13
[#100206] Rite implementation in C++? (Objective C?) — ptkwt@... (Phil Tomson) 2004/05/13

In article <40A3E539.2010204@hypermetrics.com>,

[#100193] subclasses of string as hash keys — Matthias Georgi <matti_g@...>

15 messages 2004/05/13

[#100273] Regexp Error? — "Robert Klemme" <bob.news@...>

What's wrong here?

16 messages 2004/05/14

[#100295] Re: Regexp Error? — Michael Campbell <michael_s_campbell@...>

ts wrote:

15 messages 2004/05/14

[#100325] Re: Please revisit Ruby's Best and vote for additions — "Curt Hibbs" <curt@...>

James Britt wrote:

12 messages 2004/05/14

[#100395] Need Help Selecting a GUI — dejaspam@... (Bill Atkins)

As anyone can tell by looking at the topics of my recent posts, I'm

18 messages 2004/05/15

[#100461] Ruby on Rails — Matt Lawrence <matt@...>

Very neat presentation, it kept me up way too late last night watching it.

24 messages 2004/05/16

[#100511] How to duck type? - the psychology of static typing in Ruby — Tim Bates <tim@...>

Hi all,

83 messages 2004/05/17
[#100525] Re: How to duck type? - the psychology of static typing in Ruby — "SER" <ser@...> 2004/05/17

Broken record time:

[#100791] Re: How to duck type? - the psychology of static typing in Ruby — Marek Janukowicz <childNOSPAM@...17.ds.pwr.wroc.pl> 2004/05/19

On Thu, 20 May 2004 03:43:22 +0900, Dave Thomas wrote:

[#100613] FoX: removing widgets — Yuri Leikind <y.leikind@...>

Hello all,

16 messages 2004/05/18
[#100670] Re: FoX: removing widgets — Lyle Johnson <lyle@...> 2004/05/18

Yuri Leikind wrote:

[#100629] Newbie installation problem: libraries? — Paul Emmons <pemmons@...>

I have recently installed ruby-1.8.1 on my Mandrake Linux system.

11 messages 2004/05/18

[#100649] Windows desktop app w/ simple db; how? — "Kirk Haines" <khaines@...>

Imagine that you had a very simple web based application. It queries some

12 messages 2004/05/18

[#100653] Zero is true ... whoda thunk? — "Richard Lionheart" <NoOne@...>

Hi,

29 messages 2004/05/18
[#100655] Re: Zero is true ... whoda thunk? — Mark Sparshatt <msparshatt@...> 2004/05/18

Richard Lionheart wrote:

[#100682] Re: Zero is true ... whoda thunk? — Chris Pine <cpine@...> 2004/05/19

On Wed, 19 May 2004 06:34:54 +0900, Mark Sparshatt wrote:

[#100691] Re: Zero is true ... whoda thunk? — David Naseby <david.naseby@...>

>-----Original Message-----

14 messages 2004/05/19

[#100721] irb or xterm crash with UTF-8 — Simon Strandgaard <neoneye@...>

server> irb

15 messages 2004/05/19

[#100839] Where to download FXRuby library; where to put it; RUBYLIB, RUBYPATH env. vars — "Richard Lionheart" <NoOne@...>

Hi,

14 messages 2004/05/20

[#100862] Ruby's builtin Datastructures — Brian Schroeder <spam0504@...>

Hello all,

13 messages 2004/05/20

[#101071] Concerning version numbers... — Hal Fulton <hal9000@...>

A few people in recent weeks have been bemoaning the fact that software

15 messages 2004/05/22

[#101110] Ruby for educational purposes and localization — Laurent Julliard <laurent__no__@__spam__moldus.org>

All,

10 messages 2004/05/23

[#101165] make faster Richards benchmark — dlissett0@... (Duncan Lissett)

I'd appreciate any suggestions on how to make a faster Ruby

15 messages 2004/05/24

[#101226] Concerning package names — djberg96@... (Daniel Berger)

All,

13 messages 2004/05/24

[#101292] Numeric#of — "Ara.T.Howard" <ahoward@...>

53 messages 2004/05/25

[#101329] separating ruby-talk from comp.lang.ruby? — David Alan Black <dblack@...>

Hi --

65 messages 2004/05/25
[#101388] Re: separating ruby-talk from comp.lang.ruby? — ptkwt@... (Phil Tomson) 2004/05/25

In article <m3brkcda7g.fsf@wobblini.net>,

[#101391] Re: separating ruby-talk from comp.lang.ruby? — "David A. Black" <dblack@...> 2004/05/25

Hi --

[#101360] ruby-dev summary 23459-23562 — Minero Aoki <aamine@...>

Hi all,

30 messages 2004/05/25
[#101395] Re: ruby-dev summary 23459-23562 — Florian Gross <flgr@...> 2004/05/25

Minero Aoki wrote:

[#101369] defining condititions — Florian Weber <csshsh@...>

hi!

18 messages 2004/05/25

[#101522] WEBrick and FastCGI response — Jim Freeze <jim@...>

Reply-To:

15 messages 2004/05/27

[#101560] Re: separating ruby-talk from comp.lang.ruby? — "Mills Thomas (app1tam)" <app1tam@...>

Here, here, hear, hear. No NNTP here.

41 messages 2004/05/27
[#101565] Re: separating ruby-talk from comp.lang.ruby? — "David A. Black" <dblack@...> 2004/05/27

Hi --

[#101569] Re: separating ruby-talk from comp.lang.ruby? — Dave Thomas <dave@...> 2004/05/27

[#101571] Re: separating ruby-talk from comp.lang.ruby? — "David A. Black" <dblack@...> 2004/05/27

Hi --

[#101616] Re: separating ruby-talk from comp.lang.ruby? — "David A. Black" <dblack@...> 2004/05/28

Hi --

[#101686] Re: separating ruby-talk from comp.lang.ruby? — Dave Thomas <dave@...> 2004/05/28

[#101890] Re: separating ruby-talk from comp.lang.ruby? — "David A. Black" <dblack@...> 2004/05/31

Sorry everyone, testing again.... small meaningless tweak to

[#101674] Andreas' practical language comparison — "Georgy" <no.mail@...>

Hi all!

13 messages 2004/05/28

[#101745] Test::Unit: assert_follows_spec() (or something like that) — David Garamond <lists@...6.isreserved.com>

For deterministic functions/methods, the same set of inputs should

20 messages 2004/05/29

[#101823] gsub!, replace with \' — Patrick Gundlach <clr1.10.randomuser@...>

Dear Ruby-hackers,

14 messages 2004/05/30

[#101830] Behavior of application changes when adding non-relevant puts — felix.nawothnig@... (Felix Nawothnig)

Hi.

12 messages 2004/05/30

[#101853] mysql-ruby — Paul Vudmaska <paul@...>

%$@#%$ i know i've abused this list with more questions than answers but

14 messages 2004/05/31

[#101855] elegant way to say "try this thing, one at a time, until condition is met" — David Garamond <lists@...6.isreserved.com>

Example: I need to unmount /usr and /usr2, but currently I can't because

23 messages 2004/05/31

[#101899] RMagick available on Windows — Tim Hunter <cyclists@...>

Thanks to Kaspar Schiess, RMagick for Windows is now available at

13 messages 2004/05/31

How to duck type? - the psychology of static typing in Ruby

From: Tim Bates <tim@...>
Date: 2004-05-17 13:52:22 UTC
List: ruby-talk #100511
Hi all,
Following a discussion in #ruby-lang, I have a suggestion about how to 
approach Duck Typing. Below is my dissertation on the subject. :P My 
intention is to incorporate any comments people might have into the text 
and then place it on the Wiki as an introduction to Duck Typing for the 
static typist.

For those not in on the secret, the idea is that if an object walks like 
a duck and quacks like a duck, it may as well be a duck - this being a 
metaphor for an arbitrary object that may not be exactly the same class 
your code was expecting, but still behaves the same way - see [1] if you 
don't follow.

---

Many people coming to Ruby from a statically-typed language are somewhat 
afraid of Ruby's dynamism, or "don't get it(TM)". David Black and I 
believe that this is in part because it is thought that the uncertainty 
and changeability built into Ruby are dangerous and one wants to find 
shelter from them.

Please bear with me while I describe some of the possible approaches.

1) People with a Static Typing background often have the urge to do 
something like this:

   attr_reader :date
   def date=(val)
     raise ArgumentError.new("Not a Date") if val.class != Date
   end

This is not duck typing - this is trying to get Ruby to do Static Typing.

2) Okay, you say, if that's not duck typing, let's do duck typing by 
accepting a whole bunch of different input formats and trying to turn 
them into something we know how to deal with, like this:

   def date=(val)
     class="keyword">case val
     when Date
       @date = val
     when Time
       @date = Date.new(val.year, val.month, val.day)
     when String
       if val =~ /(\d{4})\s*[-\/\\]\s*(\d{1,2})\s*[-\/\\]\s*(\d{1,2})/
         @date = Date.new($1.to_i,$2.to_i,$3.to_i)
       else
         raise ArgumentError, "Unable to parse #{val} as date"
       end
     when Array
       if val.length == 3
         @date = Date.new(val[0], val[1], val[2])
       end
     else
       raise ArgumentError, "Unable to parse #{val} as date"
     end
   end

This "normalization" approach has the advantage that the date attribute 
getter will always return a Date (producing certainty), but the setter 
can take input in a variety of formats.

2.a) Discussing this on #ruby-lang, David Black suggested the following 
optimization:

   def date=(val)
     begin
       @date = Date.new(val.year, val.month, val.day)
     rescue
       begin
         val =~ /(\d{4})\s*[-\/\\]\s*(\d{1,2})\s*[-\/\\]\s*(\d{1,2})/
         @date = Date.new($1.to_i,$2.to_i,$3.to_i)
       rescue
         begin
           @date = Date.new(val[0], val[1], val[2])
         rescue
           raise ArgumentError, "Unable to parse #{val} as date"
         end
       end
     end
   end

This has the advantage over (2) that it doesn't depend upon the class of 
val - if it acts enough like a string to use the =~ operator, then that 
clause will handle it, even if it's not descended from String - unlike 
the previous example. This makes it "more duck-typed", but still 
addresses the static-typist's fear of uncertainty and dynamism by 
providing a predictable response from #date (it will always be a Date). 
Unfortunately it's also slow.

3) Even "more duck-typed" is the approach of just testing that it 
responds to the appropriate methods, like so:

   # Accepts an object which responds to the +year+, +month+ and +day+
   # methods.
   def date=(val)
     [:year, :month, :day].each do |meth|
       raise ArgumentError unless val.responds_to?(meth)
     end
     @date = val
   end

In this case, we have removed the normalization instituted in example 
(2), but we have still ensured that the #date attribute conforms to some 
sort of interface, providing certainty. It is now the caller's 
responsibility to make sure what they pass fits the [:year, :month, 
:day] specification - but this responsibility is documented. However, 
this approach violates the Don't Repeat Yourself principle - both the 
code and the comment contain the specification, and are not therefore 
guaranteed to be in sync.

This approach is what many people believe to be embodied by "Duck 
Typing". Given an object, we're checking whether it walks and quacks 
like a duck; we're not forcing our caller to use a particular class, 
like example (1), but we are forcing our caller to put the data in a 
format we can understand, unlike (2) which attempts to deal with every 
possible representation of a date, causing volumes of maintenance work - 
imagine trying to write a normalization routine like that for every 
attribute of every class! In this way, we are moving the responsibility 
of putting the data into a reasonable format to the caller, who knows 
what format their data is in, from the receiver, who has to guess at 
every possible format the caller might send them.

4) The fourth and final approach, which I believe to be the Zen of Duck 
Typing, is as follows:

   # Accepts an object which responds to the +year+, +month+ and +day+
   # methods.
   attr_accessor :date

"What?" I hear you cry. "There's no checking there at all! You could 
pass it anything!" Yes, gentle reader, but why would you? After all, the 
documentation for this method is exactly the same as the one above. If 
the programmer using this method does what the documentation says then 
the class's behaviour is exactly the same. If they hand it the wrong 
thing (accidentally, we assume) then the only difference is that it 
breaks when the setter is called, rather than some time after the getter 
is called and we try and call a non-existent method on the result.

A common response to this often contains the phrase "meaningless error 
messages", but the results of such a mistake are usually, if not always, 
far from meaningless. For the most part, they look something like this:

   NoMethodError: undefined method `year' for "notadate":String

This tells me a lot: namely, that some part of my code (whose location 
is given in the subsequent backtrace) expected "notadate" to have a 
:year method, and it didn't. From this it is fairly trivial to deduce 
that something, somewhere, has fed the wrong thing to the date= setter 
method. Chances are that if your code is well-factored, there aren't a 
whole lot of places that set the date, and the location of the error can 
be found through a little judicious testing; you've lost the certainty 
and immediacy of the inline check, but not by much, and you've gained 
the flexibility of dynamic typing, and a whole lot less code to maintain.

Now if you'd been writing and collecting unit tests as you went along, 
instead of

   NoMethodError: undefined method `year' for "notadate":String

you would be seeing

   1) Failure:
test_stuff(MyClassTest) [./test/myclasstest.rb:13]:
<false> is not true.

which makes the error even easier to find: you go to test/myclasstest.rb 
and see something like:

10:  def test_date
11:    @obj = Foo.new
12:    @obj.date = MyClass.new.notadate
13:    assert(@obj.date.respond_to?(:year))
14:  end

and now the error is trivial to trace - the moral of the story being 
that when Duck Typing, do your checking in your unit tests, rather than 
in the live code. Type errors such as this one are usually the least 
common and easiest to trace of errors; if the attribute's documentation 
specifies what it is supposed to be, as in the example above, and the 
callers of both the getter and the setter methods make no assumptions 
about any more or less than what the documentation says, then apart from 
keyboarding accidents this will never be a problem.

At [1], Dave Thomas describes Duck Typing as "a way of thinking about 
programming in Ruby." I think he means to go a step further than that - 
Duck Typing is the _best_ way of thinking about programming in Ruby, and 
possibly the _only_ way; as David Black puts it:

"I think the concept of duck typing needs to be supplemented and 
expanded on. if, as seems to be the case, Dave thinks of it as a 
component of programming style, then it doesn't address language design 
itself. As long as duck typing is viewed as a stylistic choice, rather 
than a radical language principle, the door is always open to people 
saying 'I don't do duck typing', by which they usually mean that they 
use kind_of? a lot... of course Ruby itself *does* do duck typing, 
whether a given programmer thinks they're doing it or not."

Using kind_of? (or responds_to?) a lot isn't "not doing Duck Typing", 
it's simply adding in at run time the kinds of checks that Statically 
Typed languages do at compile time, in a usually verbose and necessarily 
incomplete fashion.

Rather than trying to make Ruby do Static Typing because one is from a 
Static Typing background and that's what one is comfortable with, one 
should become comfortable with the dynamic nature of Ruby instead. I 
have found that once I stopped assuming that the callers of my method 
(who may well be me, in five minutes time, or some user of my library on 
the other side of the planet) are stupid and don't know how to read my 
documentation (you did write some, didn't you?) then writing in Ruby 
became a whole lot more natural and somewhat less verbose. The unit 
tests took care of the psychological need to check, somewhere, that the 
method was getting passed the right thing, but in reality the whole 
debacle is a non-issue; type errors are the most trivial of bugs.

And if you're still worried about that date example, an alternative 
solution is this:

   def set_date(year, month, day)
     @date = Date.new(year, month, day)
   end

which, if year, month and day are not numeric, will catch the problem 
straight away - without resorting to Static Typing or some approximation 
of it. And the way it catches it is telling:

   irb(main):027:0> Date.new(2004.0, Rational(12,2), "17")
   ArgumentError: comparison of String with 0 failed
           from /usr/lib/ruby/1.8/date.rb:560:in `<'
           from /usr/lib/ruby/1.8/date.rb:560:in `valid_civil?'
           from /usr/lib/ruby/1.8/date.rb:590:in `new'
           from (irb):27

This is not "ArgumentError: parameters must be numbers" - the error is 
discovered when the Date class attempts to compare that parameter to 
zero and can't do it, after assuming that it was valid. And it didn't 
make the mistake any harder to find, did it? Notice that it didn't balk 
at Floats or Rationals, and with no extra coding from the implementor; 
Floats and Rationals look, and quack, like numbers. That's Duck Typing 
in action.

[1] http://rubygarden.org/ruby?DuckTyping

---

Tim.

-- 
Tim Bates
tim@bates.id.au

In This Thread

Prev Next