[PHP-DEV] State of Generics and Collections

Seems like a great plan, to be honest. I find it rather odd that Generics is the most requested PHP feature because it seems like we have survived without it for so long and I don’t subscribe to the concept that this would be the best thing for PHP since electricity.

Having an official “syntax” (Attributes, Docblock) could increase adoption. On the other hand, I feel like a likely outcome is that folks will still consider it as “something that doesn’t exist yet” and will keep requesting it.

Something else that is worth mentioning, I like that Collection is being discussed as a small step as well. It’s a very common use of Generics and would be a great addition to the language if something solid comes out of it.

···

Marco Deleu

On Fri, Aug 23, 2024, at 4:58 AM, Kévin Dunglas wrote:

Thanks for sharing this research work.

Instead of having to choose between fully reified generics and erased
type declarations, couldn't we have both? A new option in php.ini could
allow to enable the “erased” mode as a performance, production-oriented
optimization.
In development, and on projects where performance isn't critical, types
(including generics) will be enforced at runtime, but users will have
the option of opting to disable these checks for production
environments.

Strictly speaking, yes, a disabled-types mode could be made regardless of what happens with generics. But the downsides of that approach remain the same. I'm personally against type erasure generally, in large part because I don't know what it would break in terms of reflection, and in part because I *know* people will turn it off for dev, too, and then end up writing buggier code.

If this is not possible, the inline caches presented in the article,
combined with “worker” runtimes such as FrankenPHP, Swoole, RoadRunner,
etc., could make the cost of enforcing generics negligible:
technically, types will be computed once and reused for many HTTP
requests (because they are handled by the same long-running PHP script
under the hood). As working runtimes already provide a significant
performance improvement over FPM, we could say that even if
non-performance-critical applications (most applications) will be a bit
slower because of the new checks, people working on
performance-sensitive applications will have the opportunity to reduce
the cost of checks to virtually nothing by switching to a
performance-oriented runtime.

From talking to Arnaud, the main issue here is the file-at-a-time compilation. I'm not entirely clear if a persistent process would side-step that, with the delayed resolution bits, or if those would have to be re-resolved each time. (That's an Arnaud question.) Another possibility that's been floated a bit, tangentially, is allowing some kind of multi-file loading, which would allow for a larger scope to be included at once as an opcache segment, and thus the optimizer could do more.

That said, I suspect the benefits of the JIT when using a worker-mode runner would be larger anyway.

Also, speaking for me personally and no one else, I am still very much in favor of official steps to improve worker-mode options in php-src directly. What form that takes I'm not sure, but I would very much favor making worker-mode a first-class citizen, or at least a one-and-a-half class citizen, rather than its current second-class status.

--Larry Garfield

On Fri, Aug 23, 2024, at 6:48 AM, Roman Pronskiy wrote:

On Mon, Aug 19, 2024 at 7:11 PM Derick Rethans <derick@php.net> wrote:

Arnaud, Larry, and I have been working on an article describing the
state of generics and collections, and related "experiments".

You can find this article on the PHP Foundation's Blog:
State of Generics and Collections — The PHP Foundation — Supporting, Advancing, and Developing the PHP Language

Thank you Arnaud, Derick, Larry for the article.

Do you consider the path of not adding generics to the core at all? In
fact, this path is implicitly taken during the last years. So maybe it
makes sense to enforce that status quo?

Potential steps:
- Make the current status quo official by recognizing generics PHPDoc
syntax as The Generics for PHP. Just adding a php.net manual page will
do.
- Recognize Composer as the official PHP tool. It's currently not
mentioned on php.net at all.
- Suggest using PHPStan or Psalm for generics and type checks.
- Add an official specification for generics in the PHP manual to
eliminate semantic variances between tools.

This will keep the core simple and reduce the maintenance burden, not
increase it.

Moreover, it does not contradict with any other implementation
mentioned in the article, should they happen. In fact, it could be a
first baby-step for any of them.

There is also an attempt to do generics via attributes –
GitHub - php-static-analysis/attributes: Attributes used for static analysis – it could
potentially be a better alternative of recognising “official” syntax,
because unlike PHPDocs, attributes can be available in core and the
syntax is checked.

What do you folks think?

-Roman

The null option is always an option, yes. The thing to understand is that today, *we already have erased generics*, via PHPStan/Psalm. That's one reason I am, personally, against erased generics in the language proper. They don't really offer anything we don't have already.

Moving those definitions to attributes is certainly possible, though AFAIK both the PHPStan and Psalm devs have expressed zero interest in it. Part of the challenge is that such an approach will either still involve string parsing, or will involve a lot of deeply nested attribute classes. For instance, if today you'd write:

/**
* @var array<string, Dict<string, Foo>>
*/
protected array $foos;

(An entirely reasonable lookup table for some circumstances). What would that be in attributes?

This would still need string parsing:

#[GenericType('string', 'Dict<string, Foo>>')]

And a form that doesn't need string parsing:

#[DictType('string', new Dict('string', Foo::class))]

Which is getting kinda ugly fast.

All else equal, if we have to keep generics to implicit/erased, I'd favor going all the way to the latter (no-string-parsing attributes), and revising the syntax along the way. (The current syntax used by SA tools is decidedly weird compared to most generic languages, making it hard to follow.)

If instead we used attributes for reified generics, then we have all the same challenges that make reified generics hard, just with a different syntax. As I understand it (again, Arnaud is free to correct me), these two syntaxes would be equally straightforward to parse, but also equally complex to implement the runtime logic for:

#[DictType('string', new Dict('string', Foo::class))]
protected array $foo;

protected Dict<string, Dict<'string', Foo>> $foo;

The latter is more compact and typical of our sibling languages, but once the parser is done, all of the other challenges are the same.

As for making docblock generics "official", one, as I noted I hate the current syntax. :slight_smile: Two, that seems unwise as long as PHP still has an option to remove comments/docblocks at compile time. Even if it's not used much anymore, the option is still there, AFAIK.

And that's before we even run into the long-standing Internals aversion to even recognizing the existence of 3rd party tools for fear of "endorsing" anything. (With the inexplicable exception of Docuwiki.)

--Larry Garfield

Moving those definitions to attributes is certainly possible, though AFAIK both the PHPStan and Psalm devs have expressed zero interest in it.

Part of the challenge is that such an approach will either still involve string parsing,

That’s not really a challenge and would help somewhat with the current status quo where we have to guess where the type ends and the textual part of the comment begins. But it gets ugly for any type that has to include quotes (literal strings, array keys, etc). Technically one can use nowdocs, but it’s not much better: https://3v4l.org/4hpte

or will involve a lot of deeply nested attribute classes.

Yeah, that would look like Lisp’s S-exprs, but much worse - which, in my opinion, would harm adoption.

All in all, in my opinion attribute-based solutions are less ergonomic than what we already have now in docblocks.

···

Best regards,
Bruce Weirdan mailto:weirdan@gmail.com

On Fri, Aug 23, 2024, at 1:38 PM, Rob Landers wrote:

On Fri, Aug 23, 2024, at 20:27, Bruce Weirdan wrote:

On Fri, Aug 23, 2024 at 4:27 PM Larry Garfield <larry@garfieldtech.com> wrote:

Moving those definitions to attributes is certainly possible, though AFAIK both the PHPStan and Psalm devs have expressed zero interest in it.
Part of the challenge is that such an approach will either still involve string parsing,

That's not really a challenge and would help somewhat with the current status quo where we have to guess where the type ends and the textual part of the comment begins. But it gets ugly for any type that has to include quotes (literal strings, array keys, etc). Technically one can use nowdocs, but it's not much better: Online PHP editor | output for 4hpte

or will involve a lot of deeply nested attribute classes.

Yeah, that would look like Lisp's S-exprs, but much worse - which, in my opinion, would harm adoption.

All in all, in my opinion attribute-based solutions are less ergonomic than what we already have now in docblocks.

--
  Best regards,
      Bruce Weirdan mailto:weirdan@gmail.com

Thank you Larry for expressing some of the problems. Is there any
reason nesting has to be supported out of the gate? Think about type
hints. It started with some basic functionality and then grew over
time. There is no reason we have to have a new kitchen sink, oven,
dishwasher and stove when all we want is a new refrigerator.

— Rob

While I understand the temptation to "just do part of it", which comes up very often, I must reiterate once again that can backfire badly. That is only sensible when:

1. There's a very clear picture to get from A->Z.
2. The implementation of C and D cannot interfere with the design or implementation of J or K.
3. The steps along the way offer clear self-contained benefits, such that if nothing else happens, it's still a "complete" system and a win.
4. The part being put off to later isn't just putting off the "hard part".

In practice, the level at which you get all four is quite coarse, much coarser than it seems most people on this list think.

Examples of where we have done that:

* Enums. The initial Enum RFC is part one of at least 3 steps. Step 2 is pattern matching, Step 3 is ADTs/tagged unions. Those are still coming, but all three were spec'ed out in advance (1), we're fairly confident that the enum design will play nice with tagged unions (2), and enums step 1 has very clearly been hugely positive for the language (3, 4).

* Property hooks and aviz. These were designed together. They were originally a single planning document, way back in Nikita's original RFC. After effectively doing all the design work of both together, we split up the implementations to make them easier. Hooks was still a large RFC, but that was after we split things up. That meant we had a clear picture of how the two would fit together (1, 2), either RFC on its own would have been beneficial to the language even if they're better together (2, 3), and both were substantial tasks in themselves (4).

* Gina's ongoing campaign to make PHP's type juggling have some passing resemblance to logic.

With generics, the syntax isn't the hard part. The hard part is type inference, or accepting that generic-using code will just be extraordinarily verbose and clumsy. There is (as I understand from Arnaud, who again can correct me if I'm wrong) not a huge amount of difference in effort between supporting only Foo<Bar> and supporting Foo<Bar<Baz>>. The nesting isn't the hard part. The hard part is not having to type Foo<Bar> 4 times across 2 files every time you do something with generics. If that can be resolved satisfactorily (and performantly), then the road map to reified generics is reasonably visible.

So for any intermediate generics implementation, it would need to have a very clear picture to get from that initial state to the final state (without the landmines that something like readonly gave us), we'd need to be confident we're not adding any landmines, each step would need to be useful in its own right, and it would have to be breaking up the "hard work" into reasonable chunks, not just punting the hard work for later.

Leaving out nested generics doesn't achieve those.

This is also why the dedicated collections work that Derick and I were looking into has been on pause, because adding a dedicated collections syntax, and then getting full reified generics later, would lead to a very ugly mess of inconsistency. Better to wait and try to get full generics first, or confirm once and for all that it's impossible.

Strategies that MIGHT make sense in that framework, and the ones on which we are specifically requesting feedback, include:

* Type-erased generics, with the expectation that they would become enforced at some point in the future. (Though this could lead to lots of "working" code suddenly not working once the enforcement was turned on.)
* No type inference. Generics are just very verbose, deal with it. Type inference could, potentially, be added later. (Maybe; it's not guaranteed that it could be done effectively, as the writeup discusses).
* Only allow generics over simple types, not union/intersection types. Unlike nested generics, union types do increase the cost of determining compatibility considerably, so making them performant is a much bigger challenge. Maybe that challenge could be punted for later? (And if later turns into never, or 10 years from now, is that still an acceptable end-state?)

The acceptability of each of these strategies is what we were hoping to determine in feedback to the writeup.

--Larry Garfield

On Fri, Aug 23, 2024 at 4:31 PM Larry Garfield <larry@garfieldtech.com> wrote:

The null option is always an option, yes. The thing to understand is that today, *we already have erased generics*, via PHPStan/Psalm. That's one reason I am, personally, against erased generics in the language proper. They don't really offer anything we don't have already.

As for making docblock generics "official", one, as I noted I hate the current syntax. :slight_smile: Two, that seems unwise as long as PHP still has an option to remove comments/docblocks at compile time. Even if it's not used much anymore, the option is still there, AFAIK.

It seems you answered your own point here. Erased generics do bring
better syntax.

And that's before we even run into the long-standing Internals aversion to even recognizing the existence of 3rd party tools for fear of "endorsing" anything. (With the inexplicable exception of Docuwiki.)

Are you referring to this page: PHP: wiki:dokuwiki? It
doesn’t appear to be an endorsement and could be removed easily.

Additionally, there are several third-party extensions documented on
php.net, such as Swoole, Ds, Yaf, etc., which are, in a way, endorsed
by their inclusion.

On Friday, 23 August 2024 at 23:55, Rob Landers <rob@bottled.codes> wrote:

On Fri, Aug 23, 2024, at 23:06, Larry Garfield wrote:

With generics, the syntax isn't the hard part. The hard part is type inference, or accepting that generic-using code will just be extraordinarily verbose and clumsy. There is (as I understand from Arnaud, who again can correct me if I'm wrong) not a huge amount of difference in effort between supporting only Foo<Bar> and supporting Foo<Bar<Baz>>. The nesting isn't the hard part. The hard part is not having to type Foo<Bar> 4 times across 2 files every time you do something with generics. If that can be resolved satisfactorily (and performantly), then the road map to reified generics is reasonably visible.

Ok. But wasn't there something about nesting causing super-linear performance issues? So, disable nesting and don't worry about inference.
[...]
Ah, this is what I was thinking of. Thank you. Yeah, instead of "nesting" prior, I was referring to union types.

Rob, with all the kindness I can give, please condense your emails to have a semblance of sense.
This is not a bar where you are having a one on one conversation.
You are sending emails to thousands of people on a mailing list that can read you.
It would be appreciated if you could go over everything you read, digest the content, and then form a reply.
Or at the minimum, if you realize that a previous remark you made does not apply, redraft the email.
And possibly even sit on it for a bit before sending it, as you routinely come up with a point you forgot to include in your email.

Reading the mailing list is an exhausting task, especially when the volume is excessive.
As a reminder to everyone, we have rules: php-src/docs/mailinglist-rules.md at master · php/php-src · GitHub

However, in your case, please note the following rule:

If you notice that your posting ratio is much higher than that of other people, double-check the above rules. Try to wait a bit longer before sending your replies to give other people more time to digest your answers and more importantly give you the opportunity to make sure that you aggregate your current position into a single mail instead of multiple ones.

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal nor expected for your level of "seniority" (for the lack of better word) on the project.
This is not to say to stop posting and replying, just to do it in a more conscious manner for the rest of us reading you.

Best regards,
Gina P. Banyard

On Mon, Aug 19, 2024, at 12:08 PM, Derick Rethans wrote:

Hi!

Arnaud, Larry, and I have been working on an article describing the
state of generics and collections, and related "experiments".

You can find this article on the PHP Foundation's Blog:
State of Generics and Collections — The PHP Foundation — Supporting, Advancing, and Developing the PHP Language

cheers,
Derick

To offer my own answers to the questions posed:

* I am against erased generics in the language, on the grounds that we already have them via docblocks and user-space tooling. Pushing the entire ecosystem to move that existing syntax into another existing syntax that doesn't really offer the user anything new is not worth the effort or disruption it would cause. Reified, enforced generics would be worth the hassle.

* Even if I could be convinced of erased generics as a stop-gap, I would want to see a official, supported, first-party support for validating them in the PHP linter command, or similar. If that is surprisingly hard (it may be), then that would preclude erased generics for me.

* I am open to punting on type inference, or having only limited type inference for now (eg, leaving out union types). Primarily, doing so would be forward-compatible. Saying for now that you must type `foo<A>(new A())` even if it's redundant doesn't preclude the language in the future allowing `foo(new A())` that figures out the generic type for you, and the explicit code would still continue to work indefinitely. So this seems like an area that is safe to allow to grow in the future.

* I am also OK if there is a (small) performance hit for generic code, or for especially esoteric generic code. Eg, if `new Foo<A>` has a 1% performance hit vs `new Foo`, and `new Foo<A|B>` has a 5% performance hit, I can live with that. `new Foo<A|B>` is a zany edge case anyway, so if that costs a bit more, I'm fine. It would not be fine if adding generics made `new Foo` 30% slower, or if `new Foo<A>` was 30% slower than the non-generic version.

* My sense at the moment is that in/out markers for variance would not be a blocker, so I'd prefer to include those from the start. That's caveated on them not being a blocker; mainly I want to make sure we ensure they can be done without breaking things in the future, and I suspect the difference between "make sure we can do it" and "just do it" is small. (I could be wrong on that, of course.)

* I feel the same about `class Foo<A extends Something>` (Foo is generic over A, but A must implement the Something interface): We should make sure it's doable, and I suspect verifying that is the same as just doing it, so let's just do it. But if we can verify but it will be a lot more work to do, then we could postpone that.

* I could deal with the custom collection syntax, but I'd much rather have real reified generics and then build on top of that. That would be better for everyone. I'm willing to wait for it. (That gives me more time to design collections anyway. :slight_smile: )

--Larry Garfield

To throw one more question into the pot, I'd like to raise the possibility of wanting typedefs. (To use the old C/C++ term, the latter also allows a "type alias" syntax that does much the same thing.)

To use an example from the blog post:

function f(List<Entry<int,BlogPost>> $entries): Map<int, BlogPost>
{...}

there need not be an explicit `Map` class; instead something like

`type Map<X,Y> = List<Entry<X,Y>>;`

(to use Rust syntax).

So that one could write `Map<int,BlogPost>` and have it *mean* `List<Entry<int,BlogPost>>`. The blog example constructed a new Map given the List of Entries, but with the alias that would become a no-op.

Meanwhile `BlogPost` might itself be an alias for `StructuredText<\DomDocument>`. Without the aliasing therefore we're looking at `List<Entry<int,StructuredText<\DomDocument>>` and who is going to write that over and over? It would muffle use of generics.

I seem to recall that C#'s (or was it Java?) early support of generics didn't offer any sort of abbreviation mechanism and this lead to long awkward concrete types that no-one wanted to use.

Calling them "aliases" implies that they're expanded before types are matched up. If `Wibble<string>` and `Splunge<int>` both ultimately expand to `Foo<Bar<int>,Baz<string>>` then they would be considered to be the same type, despite appearances.

Since I expect that type aliases would become a desired feature, I would hesitate to allow/disallow making type inferences that could block their introduction. While the alias resolution would be an early stage that does mean it has to be able to identify any uses of a type in order to check whether it is a type alias. I can't think of any specific examples where this would be a problem, but it depends on how they actually end up looking - they should look like types.

On Sun, Aug 25, 2024, at 22:28, Gina P. Banyard wrote:

On Friday, 23 August 2024 at 23:55, Rob Landers rob@bottled.codes wrote:

On Fri, Aug 23, 2024, at 23:06, Larry Garfield wrote:

With generics, the syntax isn’t the hard part. The hard part is type inference, or accepting that generic-using code will just be extraordinarily verbose and clumsy. There is (as I understand from Arnaud, who again can correct me if I’m wrong) not a huge amount of difference in effort between supporting only Foo and supporting Foo<Bar>. The nesting isn’t the hard part. The hard part is not having to type Foo 4 times across 2 files every time you do something with generics. If that can be resolved satisfactorily (and performantly), then the road map to reified generics is reasonably visible.

Ok. But wasn’t there something about nesting causing super-linear performance issues? So, disable nesting and don’t worry about inference.

[…]

Ah, this is what I was thinking of. Thank you. Yeah, instead of “nesting” prior, I was referring to union types.

Rob, with all the kindness I can give, please condense your emails to have a semblance of sense.

This is not a bar where you are having a one on one conversation.

You are sending emails to thousands of people on a mailing list that can read you.

It would be appreciated if you could go over everything you read, digest the content, and then form a reply.

Or at the minimum, if you realize that a previous remark you made does not apply, redraft the email.

And possibly even sit on it for a bit before sending it, as you routinely come up with a point you forgot to include in your email.

Reading the mailing list is an exhausting task, especially when the volume is excessive.

As a reminder to everyone, we have rules: https://github.com/php/php-src/blob/master/docs/mailinglist-rules.md

However, in your case, please note the following rule:

If you notice that your posting ratio is much higher than that of other people, double-check the above rules. Try to wait a bit longer before sending your replies to give other people more time to digest your answers and more importantly give you the opportunity to make sure that you aggregate your current position into a single mail instead of multiple ones.

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal nor expected for your level of “seniority” (for the lack of better word) on the project.

This is not to say to stop posting and replying, just to do it in a more conscious manner for the rest of us reading you.

Best regards,

Gina P. Banyard

Hi Gina!

I hope this email finds you well. Sincerely, thank you for your feedback; it’s clear that you are addressing this issue with the best intentions.

I want to say that I understand the importance of this rule and keeping the mailing list conversations relevant, especially given the large audience. I want to also acknowledge that I have occasionally responded quickly without fully considering the impact on readability. Moving forward, I will make a conscious effort to ensure my emails are more thoroughly reviewed.

Regarding your point about condensing emails, I see where you are coming from. However, my approach has been to respond within the same thread to maintain context, which I believe helps keep the discussion more organized for threaded readers. I understand that there is probably a balance there and will be more mindful in the future.

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal

To understand just how bad I was breaking this rule, I created https://email.catcounter.guru/ for anyone on the list to see where they currently stand with their post-ratio in comparison to others. It is updated every two hours, and you can enter an email address in the top-right to unmask an email address, otherwise the email addresses are anonymous.

Best regards,

Rob

On 26/08/2024 23:24, Rob Landers wrote:

On Sun, Aug 25, 2024, at 22:28, Gina P. Banyard wrote:

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal

To understand just how bad I was breaking this rule, I created https://email.catcounter.guru/ for anyone on the list to see where they currently stand with their post-ratio in comparison to others. It is updated every two hours, and you can enter an email address in the top-right to unmask an email address, otherwise the email addresses are anonymous.

lol, nice. I somehow only managed to guess the top two. I think it's bugged. No way I can't figure out another in the top 5 :smirk:

At least you found something to keep yourself busy!

Cheers,
Bilge

On Tue, Aug 27, 2024, at 22:15, Bilge wrote:

On 26/08/2024 23:24, Rob Landers wrote:

On Sun, Aug 25, 2024, at 22:28, Gina P. Banyard wrote:

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal

To understand just how bad I was breaking this rule, I created https://email.catcounter.guru/ for anyone on the list to see where they currently stand with their post-ratio in comparison to others. It is updated every two hours, and you can enter an email address in the top-right to unmask an email address, otherwise the email addresses are anonymous.

lol, nice. I somehow only managed to guess the top two. I think it’s bugged. No way I can’t figure out another in the top 5 :smirk:

At least you found something to keep yourself busy!

Cheers,

Bilge

Hey Bilge,

You are in the top 10 for the day :wink: but not yet past a 10% posting ratio at some point in the range, which is how to end up on the graph. Otherwise it gets too noisy with very many people only sending in one or two emails per day. If you change the window to 7 days, you should show up since you’ve been pretty active in the last week.

If you get involved more than 14 days straight, you are almost guaranteed to appear on that graph. If you have an RFC, you are almost guaranteed to end up on that graph (due to replying to many people). If you get into an argument at some point, you are almost guaranteed to end up on that graph. If you do all three … well, due to my GMP RFC (https://wiki.php.net/rfc/operator_overrides_lite), I am the top poster on the list for the last 3 months, by a huge margin. It was very unpopular and I fought hard. (https://email.catcounter.guru/?range=52&window=80)

At least you found something to keep yourself busy!

I have more than enough to do; just not enough time. :smiley: Pretty standard problems. The last week of the month is pretty hectic, and I usually get little time to read emails and reply to them until the next month.

— Rob

On 27 August 2024 22:02:45 BST, Rob Landers <rob@bottled.codes> wrote:

On Tue, Aug 27, 2024, at 22:15, Bilge wrote:

On 26/08/2024 23:24, Rob Landers wrote:

On Sun, Aug 25, 2024, at 22:28, Gina P. Banyard wrote:

For the past 2–3 months, you have sent the vast majority of emails on this list, this is not what I would consider normal

To understand just how bad I was breaking this rule, I created https://email.catcounter.guru/ for anyone on the list to see where they currently stand with their post-ratio in comparison to others. It is updated every two hours, and you can enter an email address in the top-right to unmask an email address, otherwise the email addresses are anonymous.

lol, nice. I somehow only managed to guess the top two. I think it's bugged. No way I can't figure out another in the top 5 :smirk:

At least you found something to keep yourself busy!

You are in the top 10 for the day :wink: but not yet past a 10% posting ratio at some point in the range, which is how to end up on the graph. Otherwise it gets too noisy with very many people only sending in one or two emails per day. If you change the window to 7 days, you should show up since you've been pretty active in the last week.

This is off topic. Please don't spoil this thread with this. Remeber that over a thousand people receive this.

cheers
Derick