A critic of Guido's blog on Python's lambda

P

Pisin Bootvong

Kaz said:
Now imagine you had to do this with every object.

def add_five(x)
# return x + 5 <-- anonymous integer literal, not allowed!!!
five = 5 # define it first
return x + five

I mentioned that as Slippery slope fallacious argument in other reply.
[...]
That doesn't mean you can't develop scalable solutions to all kinds of
problems using Python. But it does mean that the scalability of the
overall solution comes from architectural details that are not related
to Python itself. Like, say, having lots of machines linked by a fast
network, working on problems that decompose along those lines quite
nicely.

Is there such language that allow scalability without any need for
design on the underlying architecture?


Python doesn't obscure or become obstacle in utilise those
architecture. Python allow one to design scalable architecture. So
Python IS scalable, isn't it? Only when Python prevent the up-scaling
or Python made scaled up project unmanagable that you can say that
Python is not scalable.

In 'Team scalable' axis, Python is easy to learn for average
programmer. So it is easier for Python to scale up.
'Data scalable' axis is language neutral, it depends on how you
architecture your database, etc.

'User requirement scalable' axis require both infrastructure and
language to provide:

No matter how scalable your language is, you cannot make a 100MHz/128MB
server serve 100,000 client a second over the internet.

No matter how many server and load balancing you have, you cannot
practically program gmail using purely MS-DOS bat file.
 
K

Ketil Malde

In the real world, people don't choose anonymous functions only in
these alleged cases where anonymous is best

In the real world, people do a lot of things they shouldn't.

Any feature can be abused, and poor style is possible in any
language. I just checked my code for lambdas, and they are
exclusively short half-liners passed as parameters to higher order
functions. Naming them would only complicate the code, just like
naming (other) intermediate results would.
if anonymous functions are available, they're used in even more
cases where naming would help

Perhps, but not necessarily. But how about the converse: if every
function must be named, they will be named even where naming them
hurts.

-k
 
M

michele.simionato

Ken said:
Python has a weak lambda, statements do not always
return values, it does not have macros, and I do not know if it has
special variables.

I am pretty much ignorant of Common Lisp, but I have the impression
they are the
same as Scheme parameters, i.e. thread-local dynamically scoped
variables
(feel free to correct me if I am mistaken). If I am right, here is how
you would emulate them in recent versions of Python:

import threading, time

special = threading.local()
special.x = 0

def getx():
return special.x

def set_x(value):
special.x = value
time.sleep(3-value) # thread-2 completes after thread-1
print "%s is setting x to %s" % (threading.currentThread(), getx())

if __name__ == '__main__':
print getx() # => 0
threading.Thread(None, lambda : set_x(1)).start() # => 1
threading.Thread(None, lambda : set_x(2)).start() # => 2
time.sleep(3)
print getx() # => 0

Michele Simionato
 
M

Michele Simionato

Alex Martelli wrote:
yes, if that was my only
choice, I'd much rather use small, simple Scheme than huge, complicated,
rich, powerful Common Lisp. ((But in this case I'm biased by early
experiences, since when I learned and used Lisp-ish languages there WAS
no Common Lisp, while Scheme was already there, although not quite the
same language level as today, I'm sure;-)).

Alas, today Scheme is not minimal at all. I mean, the document
describing the standard is short, but real implementations are pretty
rich.
I am also surprised by your claim in this thread that Scheme macros are
simpler than
Common Lisp macros; perhaps, you are not familiar with syntax-case.
BTW, there is still research going on on macros, for instance look at
http://srfi.schemers.org/srfi-72/srfi-72.html which is pretty nice.
Just to bring some info in yet another useless usenet flamewar.

Michele Simionato
 
A

Antoon Pardon

Op 2006-05-09 said:
Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

No it is not.
"if python required you to name every function then soon it will
require you to name every number, every string, every immediate result,
etc. And we know that is bad. Therefore requiring you to name your
function is bad!!!! So Python is bad!!!!"

I think this is a strawman. IMO requiring to name a function can
make things cumbersome.

I don't suppose anyone thinks the following is good practice.

one = 1.
lst.append(one)


Yet this practice is forced upon you in a number of cases when
you require functions to be named. Look at the following:

def incr_cnt_by_one(obj):
obj.cnt += 1

treat_all(lst, incr_cnt_by_one)

So the question I have is: Why is requiring me to give this function
a name considered a good thing, when it leads to a situation that
is considered bad practice in case of a number.
 
R

Rob Warnock

+---------------
| No matter how scalable your language is, you cannot make a 100MHz/128MB
| server serve 100,000 client a second over the internet.
+---------------

Sure you can! That's ~1000 CPU cycles/request, which [assuming at least
a 100BASE-TX NIC] is plenty to service 100K *small* requests/s... ;-}

Of course, you might have to write it in assembler on bare metal,
but the good news is that with only a 1000 cycle budget, at least
the code won't be very large! ;-}


-Rob [someone who remembers 0.5 MIPS DEC PDP-10s being used
for >100 simultaneous commercial timesharing users]
 
S

Stefan Nobis

if anonymous functions are available, they're used in even more
cases where naming would help

Yes, you're right. But don't stop here. What about expressions? Many
people write very complex expression, that are hard to understand. A
good language should forbid these abuse and don't allow expressions
with more than 2 or maybe 3 operators!

Forbid everything that might be abused and you have a perfect
language?

I like Python and I teach Python, I'm also used to Java and C#. But
one of the best solutions to problems of beginners and not so
brilliant programmers is something like the Dr. Scheme enviroment:
Choose your language level yourself. Everyting (allowed constructs,
error messages,...) will be adjusted to your choosen level. And
experts have all at their fingertips they want and need. Code isn't
blowed up and screwed with workaround for language limitations (that
are aimed at beginners and less capable programmers).

Why only adjust to the less capable people? Give everyone what they
need and want! :)
 
P

Pisin Bootvong

Rob said:
+---------------
| No matter how scalable your language is, you cannot make a 100MHz/128MB
| server serve 100,000 client a second over the internet.
+---------------

Sure you can! That's ~1000 CPU cycles/request, which [assuming at least
a 100BASE-TX NIC] is plenty to service 100K *small* requests/s... ;-}

Of course, you might have to write it in assembler on bare metal,
but the good news is that with only a 1000 cycle budget, at least
the code won't be very large! ;-}

Well, I was really asking for a service that really service something
complicate and useful though :-D

And donot forget to account for OS CPU time (well may be you can write
your own OS for it too :-D )

-Rob [someone who remembers 0.5 MIPS DEC PDP-10s being used
for >100 simultaneous commercial timesharing users]
 
R

Rob Warnock

+---------------
| Rob Warnock wrote:
| > | No matter how scalable your language is, you cannot make a
| > | 100MHz/128MB server serve 100,000 client a second over the internet.
| > +---------------
| >
| > Sure you can! That's ~1000 CPU cycles/request, which [assuming at least
| > a 100BASE-TX NIC] is plenty to service 100K *small* requests/s... ;-}
|
| Well, I was really asking for a service that really service something
| complicate and useful though :-D
+---------------

If "only" being useful is enough, 100 cycles is enough for a DNS server,
or an NTP server, or even a stub HTTP server that delivers some small
piece of real-time data, like a few realtime environmental sensors
[temperature, voltages, etc.].

+---------------
| > Of course, you might have to write it in assembler on bare metal,
| > but the good news is that with only a 1000 cycle budget, at least
| > the code won't be very large! ;-}
|
| And donot forget to account for OS CPU time (well may be you can write
| your own OS for it too :-D )
+---------------

Uh... What I meant by "bare metal" is *no* "O/S" per se, only a
simple poll loop servicing the attention flags[1] of the various
I/O devices -- a common style in lightweight embedded systems.


-Rob

[1] a.k.a. "interrupt request" bits, except with interrupts not enabled.
 
P

Pisin Bootvong

Antoon said:
Op 2006-05-09 said:
Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

No it is not.

[...]

So the question I have is: Why is requiring me to give this function
a name considered a good thing, when it leads to a situation that
is considered bad practice in case of a number.

--

Slippery Slope::
"Argumentation that A is bad, because A might lead to B, and B
to C, and we all know C is very bad."
Why is requiring me to give this function
a name considered a good thing, when it leads to a situation that
is considered bad practice in case of a number.

A === "requiring me to give function a name"
no B
C === "requiring me to give number a name"

"Argumentation that requiring one to give function a name is bad,
because that might lead to requiring one to give number a name, and we
all know that that is very bad."


Now you tell me which part of that is not Slippery slope argument.

-- Or are you trying to make a sarcastic joke? I'm sorry if I didn't
get it. --
 
C

Chris Uppal

Alex said:
I think it's reasonable to make a name a part of functions, classes and
modules because they may often be involved in tracebacks (in case of
uncaught errors): to me, it makes sense to let an error-diagnosing
tracebacks display packages, modules, classes and functions/methods
involved in the chain of calls leading to the point of error _by name_.

It seems to me that there's something of a circularity here. Either in your
logic if applied to language design in general, or as a self-perpetuating habit
when applied to Python in particular.

The assumption is that functions are in some sense rather "big" things -- that
each function performs a well-defined action which can be understood in
isolation. That may well be true in idiomatically written Python (I've never
cared for the look of the language myself, so I don't know what's considered
"normal"), but it isn't true in general. With the assumption, it makes sense
to say that every function /could/ have a name, and so, why not /give/ it a
name ? But without the assumption, when many little anonymous functions are
used, the idea of giving them all names appears pettifogging to the point of
idiocy. If the language and/or culture expects that all/most functions will be
named, then "little" functions (in my sense) won't be used; hence the
self-perpetuating habit. But I don't think that the underlying logic supports
that habit independently of its own self-perpetuating nature.

E.g. consider the Smalltalk code (assumed to be the body of a method):

aCollection
do: [:each |
each > 0 ifTrue: [^ true]].
^ false.

which iterates over a collection checking to see if any element is > 0. If so
then the method answers true ("^" -- spelled "return" in Java), otherwise it
answers false. In that code,
[^ true]
is syntactically and semantically an anonymous function, which is only invoked
if the antecedent is true (in point of fact the compiler inlines that function
away but I don't think that's relevant here). The passage beginning
[:each | ...
and reaching to the matching ] is also an anonymous function with one parameter
(each) which is applied to each element of the collection in turn. (In this
case it really is an anonymous function, even at the implementation level.)
What "name" would you give to either of them ? I don't believe that /any/ name
is possible, and certainly that no name is desirable.

In my working Smalltalk environment today, there are 60099 methods defined
across 3369 classes. In that codebase there are 38112 anonymous functions. Do
you really want to have to find names for them all ?

-- chris
 
A

Antoon Pardon

Op 2006-05-09 said:
Antoon said:
Op 2006-05-09 said:
Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

No it is not.

[...]

So the question I have is: Why is requiring me to give this function
a name considered a good thing, when it leads to a situation that
is considered bad practice in case of a number.

--

Slippery Slope::
"Argumentation that A is bad, because A might lead to B, and B
to C, and we all know C is very bad."

But I have seen noone here argue that requiring functions to be named
leads to requiring all variables to be named.
A === "requiring me to give function a name"
no B
C === "requiring me to give number a name"

"Argumentation that requiring one to give function a name is bad,
because that might lead to requiring one to give number a name, and we
all know that that is very bad."

That is not the arguement I'm making.

The argument is that a particular pratice is considered bad coding,
(with an example giving a number) and then showing that requiring
a name for a function, almost makes such a practice inevitable (for
certain functions used as parameters)
 
C

Chris Uppal

Pisin said:
Slippery Slope::
"Argumentation that A is bad, because A might lead to B, and B
to C, and we all know C is very bad."

For the Slippery Slope criticism to be applicable, there would have to be some
suggestion that removing anonymous functions /would actually/ (tend to) lead to
removing anonymous values in general. There was no such suggestion.

The form of the argument was more like reasoning by analogy: if context A has
features like context B, and in B some feature is known to be good (bad) then
the analogous feature in A is also good (bad). In that case an attack on the
validity of the argument would centre on the relevance and accuracy of the
analogy.

Alternatively the argument might be seen as a generalisation/specialisation
approach. Functions are special cases of the more general notion of values.
We all agree that anonymous values are a good thing, so anonymous functions
should be too. If you parse the argument like that, then the attack should
centre on showing that functions have relevant special features which are not
shared by values in general, and so that we cannot validly deduce that
anonymous functions are good.

-- chris
 
D

David C. Ullrich

There may be some confusion here because there are two places for code
being discussed at the same time, and two sense of propagation.

the two places for code are (1) the rule attached to A which is
responsible for computing a value for A and (2) a callback for A to be
invoked whenever A changes. Why the difference?

In Cells, A is a slot such as 'background-color'. Whenever that changes,
we have to do something more. On Mac OS9 it was "InvalidateRect" of the
widget. In Cells-Tk, it is:
(Tcl_interp "mywidget configure -background <new color>")

In my OpenGL GUI, it is to rebuild the display-list for the widget.

That is the same no matter what rule some instance has for the slot
background-color, and different instances will have different rules.

As for propagating, yes, Cells propagates automatically. More below on
that. What I saw in the example offered was a hardcoded on-change
callback that was doing /user/ propagation form B to A (and B to A! ...
doesn't that loop, btw?

No, there's no looping in the example. Yes, the code determining what
b returns should be attached to b instead of to a, but the code I
gave does work as advertised. (I mean give me a break - I provided
a sample use so you could simply run it. Installing Python is not
hard.)

If you, um, look at the code you see that "cells.a = 42" triggers
cells.__setattr__, which fires a's callback; the callback then
reaches inside and sets the value of b _without_ going through
__setattr__, hence without triggering b's callback.

In Cells you can't have A depend on B and also B depend on A?
That seems like an unfortunate restriction - I'd want to be
able to have Celsius and Farenheit, so that setting either
one sets the other.

Of course if there are no loops it's easier to see how
you managed to do the stuff you were talking about elsewhere,
(at least sometimes) delaying execution until needed.
Anyway...)
all
the *programmer* has to care about is what values a slot depends on --
Cells takes care of inverting that for you, which is important because
that's a job that a computer is much better at than a human.


Fine. I suppose that is better; if b is going to return a + 1
the fact that this is what b returns should belong to b, not
to a. So a has an update list including b, so when a's value
is set a tells b it needs to update itself.

If we're allowed to pass (at some point, to some constructor
or other) something like (b, a + 1, [a]), which sets up a
cell b that shall return a + 1, and where the [a] is used
in the constructor to tell a to add b to a's update list
then this seems like no big deal.

And doing that doesn't seem so bad - now when the programmer
is writing b he has to decide that it should return a + 1
and also explicitly state that b shall depend on a; this
is all nice and localized, it's still _b_ telling _a_ to
add b to a's update list, and the programmer only has
to figure out what _b_ depends on when he's writing _b_.
Doesn't seem so bad.

But of course it would be better to be able to pass just
something morally equivalent to (b, a + 1) to whatever
constructor and have the system figure out automatically
that since b returns a + 1 it has to add a to b's update
list. There must be some simple trick to accomplish that
(using Python, without parsing code).

Right, you do not want to parse code. It really would not work as
powerfully as Cells, which notice any dynamic access to another cell
while a rule is running. So my rule can call a function on "self" (the
instance that wons the slot being calculated, and since self can have
pointers to other instances, the algorithm can navigate high and low
calling other functions before finally reading another ruled slot. You
want to track those.
Exactly what the trick is I
don't see immediately.

To compute a value for a slot that happens to have a rule associated
with it, have a little cell datastructure that implements all this and
associate the cell with the slot and store a pointer to the rule in the
cell. Then have a global variable called *dependent* and locally:

currentdependent = *dependent*
oldvalue = cell.value
newvalue = call cell.rule, passing it the self instance
*dependent* = currentvalue

if newvalue not = oldvalue
call on-change on the slot name, self, newvalue and oldvalue
(the on-chnage needs to dispatch on as many arguments as
the language allows. Lisp does it on them all)

In the reader on a slot (in your getattr) you need code that notices if
the value being read is mediated by a ruled cell, and if the global
*dependent* is non empty. If so, both cells get a record of the other
(for varying demands of the implementation).
In Cells do we just pass a rule using other cells to
determine this cell's value, or do we also include
an explicit list of cells that this cell depends on?

Again, the former. Just write the rule, the above scheme dynamically
figures out the dependencies. Note then that dependencies vary over time
because of different branches a rule might take.

I want to reassure the community that this (nor the spreadsheet analogy
<g>) is not just my crazy idea. In 1992:

http://www.cs.utk.edu/~bvz/active-value-spreadsheet.html

"It is becoming increasingly evident that imperative languages are
unsuitable for supporting the complicated flow-of-control that arises in
interactive applications. This paper describes a declarative paradigm
for specifying interactive applications that is based on the spreadsheet
model of programing. This model includes multi-way constraints and
action procedures that can be triggered when constraints change the
values of variables."

Cells do not do multi-way constraints, btw.

My _guess_ is that a "multi-way constraint" is something like
what's above, where A depends on B and B depends on A?
Nor partial constraints. To
hard to program, because the system gets non-deterministic. That kinda
killed (well, left to a small niche) the whole research programme. I
have citations on that as well. :)

kenny


************************

David C. Ullrich
 
C

Cameron Laird

Cameron Laird said:
On this one isolated matter, though, I'm confused, Alex: I sure
think *I* have been writing DSLs as specializations of Python,
and NOT as "a language in its own right". Have I been fooling
myself, or are you making the point that Lisp-based DSLs live in
a larger syntactic universe than Python's, or ...?

With Lisp (or Scheme, for that matter), a DSL "lives" in exactly the
same world as the base language, while being able to add or tweak
whatever syntax it needs; with Python (or C++ or Java, for that matter),
a DSL is either a completely separate beast (parsed, compiled, perhaps
interpreted in the "host" language), or else it uses exactly the same
syntax as used in the host language. To rapidly build, experiment with,
and tweak, DSL's, a language with macros is thus advantaged.

As to how crucial that is for _production_ (as opposed to _research_)
purposes, well, obviously I prefer having no macros (or else I'd use
some form of Lisp, or perhaps Dylan -- at least for my own private
purposes, such as my long-standing personal research into the
fundamentals of contract bridge -- while in fact I prefer to use Python
for those purposes just as for others). But that doesn't make me blind
to the advantages of macros for DSL-building purposes (if I was totally
sold on both Python AND macros, I think I might build a macro
preprocessor on top of Python -- the current ASL-based compiler probably
makes that task much more feasible than it used to be -- but, macros
would be somewhat complicated as in Dylan, not extremely easy as in
Scheme [[or, I guess, Common Lisp, though I have no real experience with
those -- on the surface they look murkier than Scheme's, but that may be
just an issue of habit on my part]]).


Alex

Ah! I understand much better now.

You correctly distinguish, say, Lisp, Dylan, ... from Python, C,
Java, ... for their syntactic aptness at construction of DSLs.
I don't want that to mislead readers, though, who might thus fail
to see how Python *is* different from C, Java, ..., in its
convenience for implementation of a particular kind of Python-
looking DSL. As you know, it's easy (if heavy with security
consequences) in Python to eval(), and it's not in C or Java.
The Python run-time builds in a complete interpreter, and that's
different from the base functionality of C or Java.

A consequence is that, in Python, it's an exercise for a beginner
to write, say an assembler whose syntax is a sequence of function
calls (<URL: http://www.unixreview.com/documents/s=9133/ur0404e/ >).
C or Java can't really boast the same.
 
A

Alex Martelli

Stefan Nobis said:
Yes, you're right. But don't stop here. What about expressions? Many
people write very complex expression, that are hard to understand. A
good language should forbid these abuse and don't allow expressions
with more than 2 or maybe 3 operators!

That would _complicate_ the language (by adding a rule). I repeat what
I've already stated repeatedly: a good criterion for deciding which good
practices a language should enforce and which ones it should just
facilitate is _language simplicity_. If the enforcement is done by
adding rules or constructs it's probably not worth it; if the
"enforcements" is done by NOT adding extra constructs it's a double win
(keep the language simpler AND push good practices).


Alex
 
R

Randal L. Schwartz

Alex> The difference, if any, is that gurus of Java, C++ and Python get to
Alex> practice and/or keep developing their respectively favorite languages
Alex> (since those three are the "blessed" general purpose languages for
Alex> Google - I say "general purpose" to avoid listing javascript for
Alex> within-browser interactivity, SQL for databases, XML for data
Alex> interchange, HTML for web output, &c, &c), while the gurus of Lisp,
Alex> Limbo, Dylan and Smalltalk don't (Rob Pike, for example, is one of the
Alex> architects of sawzall -- I already pointed to the whitepaper on that
Alex> special-purpose language, and he co-authored that paper, too).

That's crazy. Some of the key developers of Smalltalk continue to work
on the Squeak project (Alan Kay, Dan Ingalls, and I'm leaving someone
out, I know it...). So please remove Smalltalk from that list.

--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<[email protected]> <URL:http://www.stonehenge.com/merlyn/>
Perl/Unix/security consulting, Technical writing, Comedy, etc. etc.
See PerlTraining.Stonehenge.com for onsite and open-enrollment Perl training!

*** ***
 
P

Pisin Bootvong

Antoon said:
Op 2006-05-09 said:
Antoon said:
Op 2006-05-09, Pisin Bootvong schreef <[email protected]>:
Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

No it is not.

[...]

So the question I have is: Why is requiring me to give this function
a name considered a good thing, when it leads to a situation that
is considered bad practice in case of a number.

--

Slippery Slope::
"Argumentation that A is bad, because A might lead to B, and B
to C, and we all know C is very bad."

But I have seen noone here argue that requiring functions to be named
leads to requiring all variables to be named.

If I misinterpret your intended meaning, then I'm terribly sorry.
However let me clarify how I understand your statements -- English is
not my native language but I'm quite sure I have good English skill.

you said:

So in that question, one of the your assumption is that:

"it (required function naming) ***LEADS*** to a situation that is
considered bad practice in case of a number (required number naming)"

You have not seen anyone herevariables to be named"

But obviously I have seen someone "argue that requiring functions to be
named leads to requiring **NUMBER** to be named"

And you said that requiring number to be named is bad.
Where did I misunderstand that you, with all good faith, were not
trying to say that "A leads B which is bad so "why is A considered a
good thing""? And how is it not slippery argument?

To put my response another way, I would ask:
Does Python require you to name all your number? expression? (Hint: the
answer is no)

So where did you get the idea that requiring function to be named, as
Python does, leads to requiring number to be named? Is it in a research
anywhere? Or do majority of people here agree so (that it leads to)?

That is not the arguement I'm making.

The argument is that a particular pratice is considered bad coding,
(with an example giving a number) and then showing that requiring
a name for a function, almost makes such a practice inevitable (for
certain functions used as parameters)

Your example:

def incr_cnt_by_one(obj):
obj.cnt += 1

treat_all(lst, incr_cnt_by_one)

Did not in anyway show that the number one is required to be named. 1
in that code is written literally; you didn't have to write "one = 1"
first. The function to increment obj.cnt by one is named. but the
number 1 itself is not named.
 
A

Alex Martelli

Pisin Bootvong said:
Is there such language that allow scalability without any need for
design on the underlying architecture?

Perhaps Erlang...? I have no specific experience with it, but according
to the rumor mill it forces or cajoles you into an architecture that's
quite appropriate for scaling -- i.e., sure that architecture needed to
be designed, but (if I understand Erlang correctly) it's now built into
the language and you're more or less going to use the right paradigm.


Alex
 
A

Alex Martelli

Rob Warnock said:
If "only" being useful is enough, 100 cycles is enough for a DNS server,
or an NTP server, or even a stub HTTP server that delivers some small
piece of real-time data, like a few realtime environmental sensors
[temperature, voltages, etc.].

Reminds me of Stuart Cheshire's description of how they managed to
shoehorn zeroconf (aka bonjour, the artist formerly known as rendezvous)
into a risible amount of ROM (less than 1K byte, if I recall correctly)
left in an embedded microcontroller (for a video camera, I think).
Zeroconf is at heart a few clever tricks on top of DNS (plus 169.254.*
IPs), and in the end they managed by one more clever trick (the thingy
ignores WHAT the request is for, and just spits out the same response
each and every time -- pushing the boundaries of DNS but, it seems,
still technically staying within those boundaries;-).

Not directly relevant to the scaling debate (the camera's expected to be
on a LAN, serving a few requests per second at most), but the limit
being on bits rather than cycles somehow "tastes" similar to me.


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,812
Messages
2,569,694
Members
45,478
Latest member
dontilydondon

Latest Threads

Top