User-defined init methods when all fields are initialised

18 views
Skip to first unread message

[ICR]

unread,
Oct 29, 2011, 9:35:20 PM10/29/11
to Magpie
While trying to help tackle a problem in IRC I got stumped trying to
work out why init wasn't being called when the fields had been
initialised.

I'll start with my mental model of how Magpie works, and build on that
to the problem, as the issue is probably with my mental model.

def foo(nothing)
print("nothing")
end

def foo(message is String)
print(message)
end

foo("Hello, World!")

I expect this to print "Hello, World!", which is does as `nothing`
does not match `"Hello, World!"`, but `String` does.

Moving onto init methods.

defclass Foo
val message
end

def (this == Foo) init(message)
print(message)
this init(message: message)
end

Foo new("Hello, World!")

Again, I expect this to print "Hello, World!", which id does as the
canonical initializer is `(this == Foo) init((bar))` and the `record`
does not match "Hello, World!" because it doesn't have a bar field,
but `_` does.

If all the fields are initialised

defclass Foo
var message = "Goodbye"
end

def (this == Foo) init(message)
print(message)
this init
end

Foo new("Hello, World")

The canonical initializer is `(this == Foo) init(())`. The empty
record matches "Hello, World" as it is a record with extra fields,
which is ignored and looking through the code I can't see anywhere
that checks the types before checking fields.

This means the user-defined init method isn't called, even though
intuitively to me it seems like it should.

Mike Austin

unread,
Oct 30, 2011, 3:36:30 AM10/30/11
to magpi...@googlegroups.com
I agree.. I don't know how it works, but I expect to be able to override the initializer.  I see it as a default value if none is given.

I'm amazed with the language otherwise - things just seem to work.

Mike

Bob Nystrom

unread,
Nov 3, 2011, 1:36:01 AM11/3/11
to magpi...@googlegroups.com
On Sat, Oct 29, 2011 at 6:35 PM, [ICR] <andrew...@gmail.com> wrote:
> While trying to help tackle a problem in IRC I got stumped trying to
> work out why init wasn't being called when the fields had been
> initialised.
>
> I'll start with my mental model of how Magpie works, and build on that
> to the problem, as the issue is probably with my mental model.

Your mental model is spot on.

>
> def foo(nothing)
> print("nothing")
> end
>
> def foo(message is String)
> print(message)
> end
>
> foo("Hello, World!")
>
> I expect this to print "Hello, World!", which is does as `nothing`
> does not match `"Hello, World!"`, but `String` does.
>
> Moving onto init methods.
>
> defclass Foo
> val message
> end
>
> def (this == Foo) init(message)
> print(message)
> this init(message: message)
> end
>
> Foo new("Hello, World!")
>
> Again, I expect this to print "Hello, World!", which id does as the
> canonical initializer is `(this == Foo) init((bar))` and the `record`
> does not match "Hello, World!" because it doesn't have a bar field,
> but `_` does.

This text doesn't match the code, but you're right. Here your custom
init() matches because the string "Hello, World!" doesn't match the
(message: _) record that the default init() for Foo expects.

>
> If all the fields are initialised
>
> defclass Foo
> var message = "Goodbye"
> end
>
> def (this == Foo) init(message)
> print(message)
> this init
> end
>
> Foo new("Hello, World")
>
> The canonical initializer is `(this == Foo) init(())`. The empty
> record matches "Hello, World" as it is a record with extra fields,
> which is ignored and looking through the code I can't see anywhere
> that checks the types before checking fields.

You're right. I'd never considered the empty record case. The not
checking types is at least semi-intentional: I wanted to support
passing an instance of a class to something that expected a record in
order to be able to destructure the class's fields.

>
> This means the user-defined init method isn't called, even though
> intuitively to me it seems like it should.

You're right, this is counter-intuitive. There's two things colliding:

1. Record patterns take priority over variable patterns. This is
generally good but...
2. A record pattern with no fields matches pretty much everything.

I think the fix here might just be that records with no fields should
automatically be converted to the `nothing` value. Changing that fixes
this, but it causes another issue.

When a field has a default value, you can still override that by passing one in:

defclass Foo
var bar = "default"
end

Foo new(bar: "override")

But if we replace the empty record with nothing, then actually giving
it a record no longer matches. I'll have to think some more about what
the clean way to solve this is (unless you have any ideas!)

- bob

Bob Nystrom

unread,
Nov 3, 2011, 1:36:57 AM11/3/11
to magpi...@googlegroups.com
On Sun, Oct 30, 2011 at 12:36 AM, Mike Austin
<mike.aus...@gmail.com> wrote:
> I agree.. I don't know how it works, but I expect to be able to override the
> initializer.  I see it as a default value if none is given.

That's right. In most cases you can. This is just a nasty edge case
that I haven't thought about and fixed yet. :(

>
> I'm amazed with the language otherwise - things just seem to work.

It's nice hearing someone else say that! :)

- bob

Mathnerd314

unread,
Nov 3, 2011, 3:22:07 AM11/3/11
to magpi...@googlegroups.com
If a record with no fields matches everything, you should make that
explicit with an "everything" pattern instead of trying to hack it into
a "nothing" pattern. Every type system has both a top (e.g. Object in
Java) and a bottom (e.g. void in Java); there's no reason for Magpie to
be an exception.

Personally, I don't like constructors / init methods at all, but I guess
they're difficult to escape from in an object-oriented language with
classes. Perhaps one could use a system of tags instead of a system of
classes and a delegation model like Self's. But it's probably too risky;
better keep Magpie as-is.

-- Mathnerd314

Bob Nystrom

unread,
Nov 3, 2011, 4:40:03 AM11/3/11
to magpi...@googlegroups.com
On Thu, Nov 3, 2011 at 12:22 AM, Mathnerd314 <mathner...@gmail.com> wrote:

> If a record with no fields matches everything, you should make that explicit
> with an "everything" pattern instead of trying to hack it into a "nothing"
> pattern.

What I'm getting at is that it's wrong to have a field-less record
*because* it matches everything. Magpie does have "everything"
patterns, of course, a variable pattern or a wildcard pattern will
match any object.

But you're right that using a nothing pattern here is probably the
wrong path too.

> Every type system has both a top (e.g. Object in Java)

Magpie doesn't have a static type system or a top type. C++ doesn't
have a top type either for that matter.

> and a bottom (e.g. void in Java);

I believe you're thinking of a unit type. Java doesn't really have a
bottom type. I don't think most statement-oriented languages have a
need for them. When Magpie had a static type system, it did have one
(called Never), but it doesn't now.

- bob

Mathnerd314

unread,
Nov 3, 2011, 1:20:23 PM11/3/11
to magpi...@googlegroups.com
On 11/3/2011 3:40 AM, Bob Nystrom wrote:
> On Thu, Nov 3, 2011 at 12:22 AM, Mathnerd314<mathner...@gmail.com> wrote:
>
>> If a record with no fields matches everything, you should make that explicit
>> with an "everything" pattern instead of trying to hack it into a "nothing"
>> pattern.
> What I'm getting at is that it's wrong to have a field-less record
> *because* it matches everything. Magpie does have "everything"
> patterns, of course, a variable pattern or a wildcard pattern will
> match any object.
But will they match on a method call with no arguments? If so, what does
the variable get bound to? And how do you specify that the method must
be called with exactly one argument? Can you distinguish between passing
a tuple of two values to a single-variable function and passing two
values to a function taking two arguments? Have you been worrying
constantly about creating some dark corner of the language by removing
the distinctions between arities? AAAH!! *brain explodes*

Anyways, if there are no arity distinctions (and you want to keep it
that way) then the best course of action does seem to be continuing to
let wildcard and variable patterns match everything, ensuring the
default init method uses a variable pattern rather than a record
pattern, and forbidding empty record patterns.

>
> But you're right that using a nothing pattern here is probably the
> wrong path too.

Yes. 'nothing' means a unit type, not "whatever the default init method
accepts". ;-)


>
>> Every type system has both a top (e.g. Object in Java)
> Magpie doesn't have a static type system or a top type. C++ doesn't
> have a top type either for that matter.

Magpie has a type system, even though it's not checked at compile time,
where the types are the patterns and the inhabitants of the types are
the values that match those patterns. If you added static type-checking
you would use the patterns to determine whether a method call was valid.

I was arguing that you *should* add a top type / pattern, basically
because all the other popular languages have one, but if variable
patterns match everything regardless of arity then they are already the
top type and you don't need to do anything.

I think void* in C++ is the top type, but to be honest it's been a while
since I programmed in anything besides Haskell so I could be wrong.


>
>> and a bottom (e.g. void in Java);
> I believe you're thinking of a unit type. Java doesn't really have a
> bottom type. I don't think most statement-oriented languages have a
> need for them. When Magpie had a static type system, it did have one
> (called Never), but it doesn't now.

If you could declare variables with a void type in Java, you would not
be allowed to assign them a value. So the void type has no inhabitants,
i.e. it's a bottom. I'll agree that you don't need to include a pattern
for it in Magpie, since there would be no way to call a method with a
bottom in the pattern signature (similar reasons apply for why most
languages don't let you use them explicitly); however, it still exists
in the type system even if you don't acknowledge it or have any code for
it. There's always an assumption of the form "Int and Bool cannot
overlap", and behind that is the equation "the join of Int and Bool is
the bottom type"...

-- Mathnerd314

Bob Nystrom

unread,
Nov 4, 2011, 9:41:05 PM11/4/11
to magpi...@googlegroups.com
On Thu, Nov 3, 2011 at 10:20 AM, Mathnerd314 <mathner...@gmail.com> wrote:
>> Magpie does have "everything"
>> patterns, of course, a variable pattern or a wildcard pattern will
>> match any object.
>
> But will they match on a method call with no arguments?

Magpie doesn't have a concept of a method call with no arguments. It
has method calls whose argument is the value nothing. A variable or
wildcard pattern will match that:

def foo(anything)
print("(" + anything toString + ")")
end

foo() // prints "(nothing)"

> If so, what does the
> variable get bound to?

The value "nothing" which is Magpie's unit type value.

> And how do you specify that the method must be called
> with exactly one argument? Can you distinguish between passing a tuple of
> two values to a single-variable function and passing two values to a
> function taking two arguments?

Currently, no, you can't. This is a corner case of Magpie that feels a
little weird.

> Have you been worrying constantly about
> creating some dark corner of the language by removing the distinctions
> between arities? AAAH!! *brain explodes*

Constantly, no, but yes this does worry me. I'm an empricist by nature
so my plan is to try to build up a bigger pile of Magpie code to get
experience with it and see how much of a problem it is in practice. So
far, I haven't been bitten by it too much, but we'll see how it goes.

It's definitely on my list of strange corners of the language. I try
to minimize the number of those as much as I can but I think every
language has them.

>
> Anyways, if there are no arity distinctions (and you want to keep it that
> way) then the best course of action does seem to be continuing to let
> wildcard and variable patterns match everything, ensuring the default init
> method uses a variable pattern rather than a record pattern, and forbidding
> empty record patterns.

You're right, that's probably a good solution. Now that I think about
it, that might simplify some other things too, though I'll have to
implement it to see how it works out.

>
>>
>> But you're right that using a nothing pattern here is probably the
>> wrong path too.
>
> Yes. 'nothing' means a unit type, not "whatever the default init method
> accepts". ;-)

Agreed!

>>
>>> Every type system has both a top (e.g. Object in Java)
>>
>> Magpie doesn't have a static type system or a top type. C++ doesn't
>> have a top type either for that matter.
>
> Magpie has a type system, even though it's not checked at compile time,
> where the types are the patterns and the inhabitants of the types are the
> values that match those patterns. If you added static type-checking you
> would use the patterns to determine whether a method call was valid.

Right.

>
> I was arguing that you *should* add a top type / pattern, basically because
> all the other popular languages have one, but if variable patterns match
> everything regardless of arity then they are already the top type and you
> don't need to do anything.

Exactly right. The reason that Magpie doesn't have a top type in the
sense of a root object class is because it doesn't need one: methods
that allow any argument are equivalent to methods on Object in other
languages.

> If you could declare variables with a void type in Java, you would not be
> allowed to assign them a value. So the void type has no inhabitants, i.e.
> it's a bottom.

Actually, it works more like a unit type. You *can* use void
explicitly in generics:

class Foo<T> {
T identity(T arg) { return arg; }
}

Foo<Void> foo = new Foo<Void>();
foo.identity(null);

The capitalized Void is I think only allowed in generics and it only
has a single value: null.

> I'll agree that you don't need to include a pattern for it in
> Magpie, since there would be no way to call a method with a bottom in the
> pattern signature (similar reasons apply for why most languages don't let
> you use them explicitly); however, it still exists in the type system even
> if you don't acknowledge it or have any code for it.

If it exists anywhere it's in the type of expressions that don't
return. Since everything is an expression in Magpie, everything
ostensibly has evaluates to a value of some type, but expressions like
"break" and "return" never yield a value, so their type is bottom.

Back when Magpie had a static type system, this was specifically built
into the language as a type called Never (a name I personally find
delightful). You could use it in type annotations so that your own
methods could participate in reachability analysis:

def foo(->)
return "blah"
print("unreachable") // type-checker would report an error here
end

def doesNotReturn(-> Never)
throw "something"
end

def foo(->)
doesNotReturn()
print("unreachable") // type-checker can also know that this is unreachable
end

If I ever resurrect Magpie's static type checker, I hope to bring this
back too. It was neat, I think.

> There's always an
> assumption of the form "Int and Bool cannot overlap", and behind that is the
> equation "the join of Int and Bool is the bottom type"...

Hmm, interesting. I'd never thought of a bottom type as the
intersection of two non-overlapping types. Thanks for the insight!

- bob

Mathnerd314

unread,
Nov 5, 2011, 9:14:07 PM11/5/11
to magpi...@googlegroups.com
On 11/4/2011 8:41 PM, Bob Nystrom wrote:
>
>> If you could declare variables with a void type in Java, you would not be
>> allowed to assign them a value. So the void type has no inhabitants, i.e.
>> it's a bottom.
> Actually, it works more like a unit type. You *can* use void
> explicitly in generics:
>
> class Foo<T> {
> T identity(T arg) { return arg; }
> }
>
> Foo<Void> foo = new Foo<Void>();
> foo.identity(null);
>
> The capitalized Void is I think only allowed in generics and it only
> has a single value: null.
But that's capitalized Void.
Lowercase void *is* uninhabited, insofar as there is no value x for
which void f() {return <x>;} is valid Java code.

-- Mathnerd314

Reply all
Reply to author
Forward
0 new messages