Junctions of classes, roles, etc.

2 views
Skip to first unread message

Ingo Blechschmidt

unread,
Apr 28, 2005, 9:28:41 AM4/28/05
to perl6-l...@perl.org
Hi,

so we had junctions of Code references some days ago, what's with
junctions of Class and Role objects? :)

role A { method foo() { 42 } }
role B { method foo() { 23 } }
class Test does A|B {}

my Test $test .= new;
my $ret = $test.foo; # 42|23?


role A {}
role B { method foo() { 42 } }
class Test does A|B {}

my Test $test .= new;
my $ret = $test.foo; # unthrown-exception undef|42?


--Ingo

--
Linux, the choice of a GNU | Knowledge is that which remains when what
generation on a dual AMD | is learned is forgotten. - Mr. King
Athlon! |

Thomas Sandlaß

unread,
Apr 28, 2005, 9:51:28 AM4/28/05
to perl6-l...@perl.org
Ingo Blechschmidt wrote:
> Hi,
>
> so we had junctions of Code references some days ago, what's with
> junctions of Class and Role objects? :)

I like them! In the type lattice A|B is the lub (lowest upper bound)
of A and B. And A&B is the glb (greatest lower bound) of A and B.
Both are cases of multiple inheritance with possible conflicts. But
A|B is more general than each of A and B, while A&B is more specific
than both.


> role A { method foo() { 42 } }
> role B { method foo() { 23 } }
> class Test does A|B {}

Here you have to implement &Test::foo.
Not doing so is a compile time error---or more
precisly a class composition time error.


> my Test $test .= new;
> my $ret = $test.foo; # 42|23?

Whatever &Test::foo returns.


> role A {}
> role B { method foo() { 42 } }
> class Test does A|B {}

Here is no conflict, but it might be necessary to
implement the more general behaviour.


> my Test $test .= new;
> my $ret = $test.foo; # unthrown-exception undef|42?

No, $ret == 42 unless Test::foo() is implemented and returns
something else.


Regards,
--
TSa (Thomas Sandlaß)


Aaron Sherman

unread,
Apr 28, 2005, 10:17:26 AM4/28/05
to Thomas Sandlaß, Perl6 Language List
On Thu, 2005-04-28 at 09:51, Thomas Sandlaß wrote:
> Ingo Blechschmidt wrote:
> > Hi,
> >
> > so we had junctions of Code references some days ago, what's with
> > junctions of Class and Role objects? :)
>
> I like them! In the type lattice A|B is the lub (lowest upper bound)
> of A and B. And A&B is the glb (greatest lower bound) of A and B.

That's not at all intuitively obvious because what you're describing is
not a junction. At least it doesn't seem like one to me (you're
describing a situation where the new class has a specific kind of
inheritance, not the situation where it's first ancestor is either all
or one of a set). The difference being that in the case of:

class x is y&z {}
my x $a;
$a.foo();

The method "foo" will be invoked from both y and z simultaneously,
without conflict, and return a junction of the two results.

> > role A { method foo() { 42 } }
> > role B { method foo() { 23 } }
> > class Test does A|B {}
>
> Here you have to implement &Test::foo.
> Not doing so is a compile time error---or more
> precisly a class composition time error.

Let's be clear about why you think that. Roles are not ordered, and thus
you must disambiguate the use of more than one. S12 says:

There are several ways to solve method conflicts. The first is
simply to write a class method that overrides the conflicting
role methods, perhaps figuring out which role method to call.

Alternately, if the role's methods are declared multi, they can
be disambiguated based on their long name. If the roles forget
to declare them as multi, you can force a multi on the roles'
methods by installing a multi stub in the class being
constructed:

multi method shake {...}

However, this is a junction, so:

class Test does A|B

is really saying that you compose two different classes called "Test",
which you refer to singly through the magic of junctions. One "Test" is
composed of the base Test class plus A, and another is composed of the
base Test class plus B (well A plus Test and B plus Test if you think of
it in terms of which overrides which).

Now, I'm not saying that that's the way it MUST be, just that that seems
to be the way that junctions would work in that situation. If we decide
that | and & aren't really junction constructors in class/role
definitions, then we can make them whatever we want.


--
Aaron Sherman <a...@ajs.com>
Senior Systems Engineer and Toolsmith
"It's the sound of a satellite saying, 'get me down!'" -Shriekback


Thomas Sandlaß

unread,
Apr 28, 2005, 11:25:09 AM4/28/05
to Perl6 Language List
Aaron Sherman wrote:
> Now, I'm not saying that that's the way it MUST be, just that that seems
> to be the way that junctions would work in that situation.

I know, and I'm very confused about all these pseudo procedural uses
of junctions. And others seem to share my state of affairs.


> If we decide
> that | and & aren't really junction constructors in class/role
> definitions, then we can make them whatever we want.

Ohh, yes please! Actually I see the biggest potential of
junctions in the type system. E.g. the comparison operator <=>
could get return type -1^0^1. This is first of all good documentation
and secondly preserves valuable information for the optimizer.
The none() junction is most usefull for excluding types---e.g junctions.

So, in the extreme Perl6 might have a junctive type calculation
overlaying the value calculation. Thus when a junctive class is
needed the class composer can be triggered to make an instance thereof.
As you pointed out that might be possible and then continue to MMD
with that newly created class. Sounds very dynamic too me. And I've
not heard about a language that throws exceptions that say "Can't create
class Foo does A|B because of conflicting methods bar()".

David Storrs

unread,
Apr 29, 2005, 1:22:53 PM4/29/05
to perl6-l...@perl.org
On Thu, Apr 28, 2005 at 03:28:41PM +0200, Ingo Blechschmidt wrote:

> so we had junctions of Code references some days ago, what's with
> junctions of Class and Role objects? :)


Could we see some code that shows why this is a good idea? My initial
reaction is horror; I can very easily see huge numbers of subtle,
hard-to-reproduce bugs coming out of this. On the other hand, I do
not immediately see major applications...most of what I can see is
things that reduce the amount of code needed, but don't actually
accomplish anything fundamentally new. What do junctions of
Class|Role objects give us that can't be achieved in other ways?

I'm quite willing to believe that there are such things, but I'm not
coming up with them.

--Dks

Brent 'Dax' Royal-Gordon

unread,
Apr 29, 2005, 10:24:42 PM4/29/05
to perl6-l...@perl.org

What do you think this is?

sub foo(Str | Int $bar) { ... }

Or this one, which is even more important?

sub foo(Any | Junction $bar) { ... }

--
Brent 'Dax' Royal-Gordon <br...@brentdax.com>
Perl and Parrot hacker

"I used to have a life, but I liked mail-reading so much better."

Autrijus Tang

unread,
Apr 30, 2005, 10:24:29 AM4/30/05
to Abhijit Mahabal, perl6-l...@perl.org
On Sat, Apr 30, 2005 at 09:13:26AM -0500, Abhijit Mahabal wrote:
> I do not see how any auto-threading occurs in that code. It is completely
> innocuous in that sense, and I don't think that is what horrified David.
> What was troublesome was, I think:
> my Str|Int $x;
> $x.foo(); # runs two methods and returns a junction

That would be absolutely horrible.

> I would like to be able to read the above code to mean:
> type X ::= Scalar where Str|Int;
> my X $x; # $x = non int/non string now a runtime error
> $x.foo(); # no different from if you had just said my $x

This is my current understanding in the implementation.

Thanks,
/Autrijus/

Abhijit Mahabal

unread,
Apr 30, 2005, 10:13:26 AM4/30/05
to perl6-l...@perl.org
On Fri, 29 Apr 2005, Brent 'Dax' Royal-Gordon wrote:

> David Storrs <dst...@dstorrs.com> wrote:
>> On Thu, Apr 28, 2005 at 03:28:41PM +0200, Ingo Blechschmidt wrote:
>>> so we had junctions of Code references some days ago, what's with
>>> junctions of Class and Role objects? :)
>>
>> Could we see some code that shows why this is a good idea? My initial
>> reaction is horror; I can very easily see huge numbers of subtle,
>> hard-to-reproduce bugs coming out of this. On the other hand, I do
>> not immediately see major applications...most of what I can see is
>> things that reduce the amount of code needed, but don't actually
>> accomplish anything fundamentally new. What do junctions of
>> Class|Role objects give us that can't be achieved in other ways?
>>
>> I'm quite willing to believe that there are such things, but I'm not
>> coming up with them.
>
> What do you think this is?
>
> sub foo(Str | Int $bar) { ... }

I believe you mean sub foo(Str^Int $bar){...} ( something that is Int or
Str but not both). But that, too, just reduces the amount of code and is
merely a shortcut for:
multi sub(Str $bar){...}
multi sub(Int $bar){...}

I do not see how any auto-threading occurs in that code. It is completely
innocuous in that sense, and I don't think that is what horrified David.
What was troublesome was, I think:
my Str|Int $x;
$x.foo(); # runs two methods and returns a junction

I would like to be able to read the above code to mean:


type X ::= Scalar where Str|Int;
my X $x; # $x = non int/non string now a runtime error
$x.foo(); # no different from if you had just said my $x

Feel free to tell me I am barking up the wrong tree if that is what I am
doing.

--abhijit

Aaron Sherman

unread,
Apr 30, 2005, 4:16:26 PM4/30/05
to Autrijus Tang, Perl6 Language List
On Sat, 2005-04-30 at 22:24 +0800, Autrijus Tang wrote:
> On Sat, Apr 30, 2005 at 09:13:26AM -0500, Abhijit Mahabal wrote:
> > I do not see how any auto-threading occurs in that code. It is completely
> > innocuous in that sense, and I don't think that is what horrified David.
> > What was troublesome was, I think:
> > my Str|Int $x;
> > $x.foo(); # runs two methods and returns a junction
>
> That would be absolutely horrible.

Then tell me what $!.can("chars") returns, assuming that $! is
implemented as an "any" junction of Int and Str values? My take would be
that it returns false|true, which is true in a boolean context, but feel
free to talk me out of it.

Str|Int is simply the type of "Yes"|1, isn't it? That would certainly
make signature matching on different kinds of junctive types trivial.

> > I would like to be able to read the above code to mean:
> > type X ::= Scalar where Str|Int;
> > my X $x; # $x = non int/non string now a runtime error
> > $x.foo(); # no different from if you had just said my $x
>
> This is my current understanding in the implementation.

That's not a junction, and thus should not use junction syntax.

I'm not opposed to having such a construct, but re-using junction syntax
is going to cause massive headaches for anyone trying to learn the
language.

Also, what is:

$x = ::("Int") | ::("Str")
my ::($x) $y;

at which stages of the execution of that code? Are you saying that $x
does not contain a junction, or that a junction used as a type does not
create a junction value?

If the latter, then what is the type of "Yes"|1?


Abhijit Mahabal

unread,
Apr 30, 2005, 5:17:06 PM4/30/05
to Aaron Sherman, Autrijus Tang, Perl6 Language List
On Sat, 30 Apr 2005, Aaron Sherman wrote:

> On Sat, 2005-04-30 at 22:24 +0800, Autrijus Tang wrote:
>> On Sat, Apr 30, 2005 at 09:13:26AM -0500, Abhijit Mahabal wrote:
>>> I do not see how any auto-threading occurs in that code. It is completely
>>> innocuous in that sense, and I don't think that is what horrified David.
>>> What was troublesome was, I think:
>>> my Str|Int $x;
>>> $x.foo(); # runs two methods and returns a junction
>>
>> That would be absolutely horrible.
>
> Then tell me what $!.can("chars") returns, assuming that $! is
> implemented as an "any" junction of Int and Str values?

If $! were explicitely "Yes"|1, then I agree that it returns false|true.

I believe that in the code you wrote..

> $x = ::("Int") | ::("Str")
> my ::($x) $y;

$x is a junction, and $y is not a junction. "type", to me, is just a
sticky note attched to an object. I would not like to see autothreading on
every junctive sticky. In the code above, if there were a method called
.type(), then $y.type() would indeed return a junction, but not by running
two methods and orring their output.

>
> Str|Int is simply the type of "Yes"|1, isn't it?

Hmmm... I am not sure. Maybe the type is just "Junc" or something.
Str|Int is also the type of 7 and of "yes".

Moreover, if the return type of a method is junctive, would you want that
menthod to autothread too?

--abhijit

Abhijit Mahabal http://www.cs.indiana.edu/~amahabal/

Brent 'Dax' Royal-Gordon

unread,
Apr 30, 2005, 7:55:48 PM4/30/05
to Aaron Sherman, Autrijus Tang, Perl6 Language List
Aaron Sherman <a...@ajs.com> wrote:
> On Sat, 2005-04-30 at 22:24 +0800, Autrijus Tang wrote:
> > On Sat, Apr 30, 2005 at 09:13:26AM -0500, Abhijit Mahabal wrote:
> > > I do not see how any auto-threading occurs in that code. It is completely
> > > innocuous in that sense, and I don't think that is what horrified David.
> > > What was troublesome was, I think:
> > > my Str|Int $x;
> > > $x.foo(); # runs two methods and returns a junction
> >
> > That would be absolutely horrible.
>
> Then tell me what $!.can("chars") returns, assuming that $! is
> implemented as an "any" junction of Int and Str values? My take would be
> that it returns false|true, which is true in a boolean context, but feel
> free to talk me out of it.

`$!` is an `Exception` (or somesuch) object, not a `Str|Int`, but in
general, that depends on the contents of the variable. If a Str|Int
was assigned a `Str`, `can` would be true; if it were assigned an
`Int`, `can` would be false. If it were assigned a disjunction of a
`Str` and an `Int`, it'd return `true|false`, which evaluates to
`true`.

> Str|Int is simply the type of "Yes"|1, isn't it? That would certainly
> make signature matching on different kinds of junctive types trivial.

Nope. The type `Str|Int` doesn't mean "this variable contains a
disjunction of `Str`s and `Int`s"; it means "This variable can contain
either a `Str` or an `Int`." (Actually, it means "this variable can
contain anything consistent with a Str or Int", which also includes
subclasses and certain junctions.) When you see a declaration like:

my Foo $bar;

Think of it as being like:

my $bar where { $_ ~~ Foo };

> If the latter, then what is the type of "Yes"|1?

I suspect it's `Disjunction of Str | Int`.

Aaron Sherman

unread,
May 1, 2005, 10:59:59 AM5/1/05
to br...@brentdax.com, Perl6 Language List
On Sat, 2005-04-30 at 16:55 -0700, Brent 'Dax' Royal-Gordon wrote:
> Aaron Sherman <a...@ajs.com> wrote:
> > On Sat, 2005-04-30 at 22:24 +0800, Autrijus Tang wrote:

> > > That would be absolutely horrible.

> > Str|Int is simply the type of "Yes"|1, isn't it? That would certainly


> > make signature matching on different kinds of junctive types trivial.
>
> Nope.

You all seem to have some very strong opinions, and I'm unable to have a
discussion around absolutes like "absolutely horrible" and "nope", so
all hypotheticals aside, I'll just concede that this is an uninteresting
way to spend our time.


Autrijus Tang

unread,
May 1, 2005, 12:48:21 PM5/1/05
to Aaron Sherman, br...@brentdax.com, Perl6 Language List
On Sun, May 01, 2005 at 10:59:59AM -0400, Aaron Sherman wrote:
> On Sat, 2005-04-30 at 16:55 -0700, Brent 'Dax' Royal-Gordon wrote:
> > Aaron Sherman <a...@ajs.com> wrote:
> > > On Sat, 2005-04-30 at 22:24 +0800, Autrijus Tang wrote:
>
> > > > That would be absolutely horrible.
>
> You all seem to have some very strong opinions, and I'm unable to have a
> discussion around absolutes like "absolutely horrible" and "nope", so
> all hypotheticals aside, I'll just concede that this is an uninteresting
> way to spend our time.

Sorry. I'll watch my language better in the future, and provide better
rationales to support my opinion. :-/

In this case, the reason I called this "absolutely horrible":

my Str|Int $x;
$x.method(); # calls .method twice

Is because to me, it is conceptually no difference to:

sub foo (Str|Int $x) { $x.method() };

In both cases, the junctive type is used to construct a new type that is a
supertype of both Str and Int, for type checking purposes. If it
silently promotes the variable itself to a Junction for autothreading,
that will render such type construction unusable, as it will be doing
two highly orthogonal things with a single syntax.

Again, my apologies for using strong words without adequate justification.

Thanks,
/Autrijus/

David Storrs

unread,
May 1, 2005, 5:58:25 PM5/1/05
to Abhijit Mahabal, perl6-l...@perl.org
On Sat, Apr 30, 2005 at 09:13:26AM -0500, Abhijit Mahabal wrote:
> On Fri, 29 Apr 2005, Brent 'Dax' Royal-Gordon wrote:
>
> >David Storrs <dst...@dstorrs.com> wrote:
> >>Could we see some code that shows why this is a good idea? My initial
> >>reaction is horror; I can very easily see huge numbers of subtle,
> >>hard-to-reproduce bugs coming out of this.
> >>I'm quite willing to believe that there are [good results], but I'm not
> >>coming up with them.


> >What do you think this is?
> >
> > sub foo(Str | Int $bar) { ... }
>
> I believe you mean sub foo(Str^Int $bar){...} ( something that is Int or
> Str but not both). But that, too, just reduces the amount of code and is
> merely a shortcut for:
> multi sub(Str $bar){...}
> multi sub(Int $bar){...}
>
> I do not see how any auto-threading occurs in that code. It is completely
> innocuous in that sense, and I don't think that is what horrified
> David.

Indeed, you're right on the money, Abhijit. Thanks for stating it so
well.


Let's move this away from simple types like Str and Int for a moment.
Tell me what this does:


class Tree {
method bark() { die "Cannot instantiate a Tree--it is abstract!" }
}
class Birch {
method bark() { return "White, papery" }
}
class Oak {
method bark() { return "Dark, heavy" }
}
class Dog {
method bark() { print "Woof, woof!"; return "bow wow" }
}
class AlienBeastie isa Tree isa Dog {}
class WhiteAlienBeastie isa Birch isa Dog {}
class HeavyAlienBeastie isa Oak isa Dog {}

sub Foo(Tree|Dog $x) { $x.bark() }


Ignore for the moment that this is stupid code--it's semantically and
(AFAIK) syntactically valid. So, what happens when I do each of these:

Foo( new AlienBeastie() );
Foo( new WhiteAlienBeastie() );
Foo( new HeavyAlienBeastie() );

??

--Dks

Thomas Sandlaß

unread,
May 2, 2005, 12:49:10 PM5/2/05
to perl6-l...@perl.org
David Storrs wrote:
> Let's move this away from simple types like Str and Int for a moment.

If you consider them simple...


> Tell me what this does:
>
>
> class Tree {
> method bark() { die "Cannot instantiate a Tree--it is abstract!" }
> }
> class Birch {
> method bark() { return "White, papery" }
> }
> class Oak {
> method bark() { return "Dark, heavy" }
> }
> class Dog {
> method bark() { print "Woof, woof!"; return "bow wow" }
> }

Four 'pure' classes so far.


> class AlienBeastie isa Tree isa Dog {}

Here you get an error/warning of a composition time conflict between
&Tree::bark and &Dog::bark. BTW, it's 'is' not 'isa'. My preferred
syntax for multiple inheritance is the junctive notation 'is Tree & Dog'
for subclassing because it nicely allows for superclassing with
'is Tree | Dog'.

> class WhiteAlienBeastie isa Birch isa Dog {}

Same for &Birch::bark and &Dog::bark.

> class HeavyAlienBeastie isa Oak isa Dog {}

Same.


> sub Foo(Tree|Dog $x) { $x.bark() }

This might dispatch correctly for 'pure' Trees, Dogs etc.
but not for your mixed classes above.

Abhijit Mahabal

unread,
May 2, 2005, 2:17:30 PM5/2/05
to Thomas Sandlaß, perl6-l...@perl.org
On Mon, 2 May 2005, [ISO-8859-1] Thomas Sandlaß wrote:

> David Storrs wrote:
>> Tell me what this does:
>>
>>
>> class Tree { method bark() { die "Cannot instantiate a Tree--it is
>> abstract!" }
>> }
>> class Birch { method bark() { return "White, papery" }
>> }
>> class Oak { method bark() { return "Dark, heavy" }
>> }
>> class Dog {
>> method bark() { print "Woof, woof!"; return "bow wow" }
>> }
>
> Four 'pure' classes so far.
>
>
>> class AlienBeastie isa Tree isa Dog {}
>
> Here you get an error/warning of a composition time conflict between
> &Tree::bark and &Dog::bark.

I don't think so; I had come to the same conclusion before realising that
we are talking inheritance here, not roles. So no trouble at class
composition time. Superclasses do not enter the picture while composing.

You'd be right if s/isa/is/ and then s/is/does/, of course.

When you dispatch, what happens would depend upon WALKMETH (according to
the pseudocode for CALLONE in A12). Usually the first inherited method
would get called.

> TSa (Thomas Sandlaß)

regards,
abhijit

David Storrs

unread,
May 3, 2005, 12:14:20 AM5/3/05
to perl6-l...@perl.org
On Mon, May 02, 2005 at 06:49:10PM +0200, Thomas Sandlaß wrote:
> David Storrs wrote:
> >Let's move this away from simple types like Str and Int for a moment.
>
> If you consider them simple...

When compared to

"arbitrary-class-that-was-defined-by-
arbitrary-programmer-of-
arbitrary-and-unknown-skill-level"

then yes, I consider them to be extremely simple.


> >Tell me what this does:
> >
> >class Tree {
> > method bark() { die "Cannot instantiate a Tree--it is abstract!" }
> >}
> >class Birch {
> > method bark() { return "White, papery" }
> >}
> >class Oak {
> > method bark() { return "Dark, heavy" }
> >}
> >class Dog {
> > method bark() { print "Woof, woof!"; return "bow wow" }
> >}
>
> Four 'pure' classes so far.

Dog is not pure according to the definition of "pure class" that I
know (no side effects in any method, constructor, or subclass). Maybe
'pure class' means something else to you?


> >class AlienBeastie isa Tree isa Dog {}
>
> Here you get an error/warning of a composition time conflict between
> &Tree::bark and &Dog::bark.

So that I'm clear, is this your opinion/preference or is it based on
material from the SEAs? If the latter, could you point me to the
relevant passage?

> My preferred
> syntax for multiple inheritance is the junctive notation 'is Tree & Dog'
> for subclassing because it nicely allows for superclassing with
> 'is Tree | Dog'.

Again, so that I'm clear--this is your preference, and is not derived
from any authoritative source, correct?


> This might dispatch correctly for 'pure' Trees, Dogs etc.
> but not for your mixed classes above.

As I noted above, Dog is not 'pure' by the definition I know so I'm
not sure what to make of this statement.

--Dks

Luke Palmer

unread,
May 2, 2005, 10:36:50 PM5/2/05
to perl6-l...@perl.org
David Storrs writes:
> On Mon, May 02, 2005 at 06:49:10PM +0200, Thomas Sandlaß wrote:
> > David Storrs wrote:
> > >class Tree {
> > > method bark() { die "Cannot instantiate a Tree--it is abstract!" }
> > >}
> > >class Birch {
> > > method bark() { return "White, papery" }
> > >}
> > >class Oak {
> > > method bark() { return "Dark, heavy" }
> > >}
> > >class Dog {
> > > method bark() { print "Woof, woof!"; return "bow wow" }
> > >}
> > >class AlienBeastie isa Tree isa Dog {}
> >
> > Here you get an error/warning of a composition time conflict between
> > &Tree::bark and &Dog::bark.
>
> So that I'm clear, is this your opinion/preference or is it based on
> material from the SEAs? If the latter, could you point me to the
> relevant passage?

As has been pointed out, there's no composition going on. But we are
getting rid of search order problems, so you'll get an ambiguous method
call error at some point. Whether this is at the time you create the
class or the time you try to call the method, I do not know.

>
> > My preferred
> > syntax for multiple inheritance is the junctive notation 'is Tree & Dog'
> > for subclassing because it nicely allows for superclassing with
> > 'is Tree | Dog'.
>
> Again, so that I'm clear--this is your preference, and is not derived
> from any authoritative source, correct?

Not authoritative. I, for one, really want the ability to superclass a
posteriori, but I'm not sure this is how you do it.

Luke

Thomas Sandlaß

unread,
May 4, 2005, 6:36:14 AM5/4/05
to perl6-l...@perl.org
Abhijit Mahabal wrote:
> When you dispatch, what happens would depend upon WALKMETH (according to
> the pseudocode for CALLONE in A12). Usually the first inherited method
> would get called.

Ohh, yes, that thing. I forget about it. And actually I hope that
there's a version among the standard pragmas that gives an error.
Or actually this option should go to the typechecker and then the
WALKMETH would nicely find a single, most specific method to call :)

But the important thing for me in this thread is that there are no
junctive bark methods in the alien beast classes! Well, unless the
WALKMETH of choice implements them =:)
--
TSa (Thomas Sandlaß)

Reply all
Reply to author
Forward
0 new messages