concatenation of arbitrary objects

12 views
Skip to first unread message

Alexander Solovyov

unread,
Apr 21, 2011, 5:36:23 AM4/21/11
to magpi...@googlegroups.com
Hi Bob,

I've just read your post on multimethods and am glad to see language
development - it's evolving to something really nice.

But there is one small thing which bothers me. This one:

def (left) + (right) concatenate(left string, right string)

I've just updated interpreter and checked this out and indeed it works this way:

> "a" + 1
= a1

> [1, 2] + 1
= <List>1

I feel that this basically makes language have weak typing, as
JavaScript does. And in JS it's a major pain that when you append
something, you get something like that. So maybe + shouldn't be an
operation on anything. I know that's more or less high level behavior,
which is easy to change, but still this seems so wrong to me that I
decided it should be discussed.

And another thing, you can't write methods in REPL:

> def fib(0) 0
! Parse error: A method body must be a block.

And... maybe I'm blind, but is there any way to see all methods
defined on an object? Like dir(something) in Python.

--
Alexander

Robert Nystrom

unread,
Apr 21, 2011, 11:24:32 AM4/21/11
to magpi...@googlegroups.com
On Thu, Apr 21, 2011 at 2:36 AM, Alexander Solovyov <alex...@solovyov.net> wrote:
I've just read your post on multimethods and am glad to see language
development - it's evolving to something really nice.

Thanks!
 
> "a" + 1
= a1

> [1, 2] + 1
= <List>1

I feel that this basically makes language have weak typing, as
JavaScript does.

That's a good point. All this stuff is brand new, so I'm still working things out. I think the question here is what did the programmer intend when they did "[1, 2] + 1"? If the answer is they wanted to concatenate an item to the list, then we should probably just define:

def (list List) + (item) list add(item)

If the answer is that it was an error, then we should tighten the default "+" to not allow any type. We could do something like:

def (left Stringable) + (right Stringable) concatenate(left string, right string)

Then you have to have your class explicitly inherit from Stringable to opt-in. The challenge here is that most types probably *are* stringable (like List) so it's hard to see if that would help.

The strictest option would be no default "+" at all, and instead we'd define specific methods for each pair we wanted to allow (string+string, int+string, string+int, etc.) I'm not sure if the combinatorial explosion would be a pain or not though.

A lot of this is stuff that still needs to be figured out and stuff that really excites me. Part of the reason I started Magpie is because I really like API design and was frustrated by what existing languages let me express, so it'll be interesting to figure out what good design looks like in a language with multimethods.
 
And another thing, you can't write methods in REPL:

> def fib(0) 0
! Parse error: A method body must be a block.

You can, but the method syntax isn't exactly how I wrote it in the post. The current grammar doesn't allow single-line methods, so you have to do:

def fib(0)
    0
end

The reason is that getters don't have any separation between the the name and the body:

def (n Int) negate - n // how do you tell that "negate" is the name and "- n" is the body?

So they require a newline as the separate:

def (n Int) negate
    - n
end

So, to be consistent, all methods do. This is one my list of syntax problems to fix so if you have any ideas here I'd definitely like to hear them.

And... maybe I'm blind, but is there any way to see all methods
defined on an object? Like dir(something) in Python.

Heh, nope, not yet. Things are still very primitive so there isn't much helpful functionality yet. :)

- bob

spir

unread,
Apr 21, 2011, 1:25:54 PM4/21/11
to magpi...@googlegroups.com

I think one right is restricting --if only by convention-- to given domains. In
particular, reserve + - * / ^ (power) to arithmetics / number types; and have
an operator dedicated to string concat only (I'll use ~).
Strings are special and complicated enough, and string concat so common, that I
guess it's really worth it. As a nice side-effect, since there is no ambiguity
thank to the operator beeing reserved (even if not concretely enforced), then
one can safely 'toString' operands:
"a" ~ 1 --> "a1"
[1, 2] ~ 1 --> "[1, 2]1"

>> And another thing, you can't write methods in REPL:
>>
>>> def fib(0) 0
>> ! Parse error: A method body must be a block.
>>
>
> You can, but the method syntax isn't exactly how I wrote it in the post. The
> current grammar doesn't allow single-line methods, so you have to do:
>
> def fib(0)
> 0
> end
>
> The reason is that getters don't have any separation between the the name
> and the body:
>
> def (n Int) negate - n // how do you tell that "negate" is the name and "-
> n" is the body?
>
> So they require a newline as the separate:
>
> def (n Int) negate
> - n
> end
>
> So, to be consistent, all methods do. This is one my list of syntax problems
> to fix so if you have any ideas here I'd definitely like to hear them.

What about replacing newline by "do"?
A better solution imo is getting rid alltogether with this exceptional syntax
for func defs and only keep the regular:

negate = fn (n Int) - n ' guess there is no need for "end" here?
negate = fn (n Int)
- n
end

I guess in programming language design all non absolutely necessary occurrences
of alternatives are wrong; if only, because people tend to expect different
idioms to have different meanings (esp in computing, unlike in natural
languages). Here, the cost is five easy to type characters: " = fn ".

denis
--
_________________
vita es estrany
spir.wikidot.com

Robert Nystrom

unread,
Apr 21, 2011, 2:25:51 PM4/21/11
to magpi...@googlegroups.com
I think one right is restricting --if only by convention-- to given domains. In particular, reserve + - * / ^ (power) to arithmetics / number types; and have an operator dedicated to string concat only (I'll use ~).
Strings are special and complicated enough, and string concat so common, that I guess it's really worth it. As a nice side-effect, since there is no ambiguity thank to the operator beeing reserved (even if not concretely enforced), then one can safely 'toString' operands:
   "a" ~ 1             --> "a1"
   [1, 2] ~ 1          --> "[1, 2]1"

That was actually the operator Magpie used for a while when operators couldn't be overloaded. I don't have a strong preference one way or the other, though "+" has value for being familiar from many other languages. Over the long term, my goal is to make string interpolation the more common way to build strings.

What about replacing newline by "do"?

Interesting idea:

def (_ IndexableIterator) current do this indexable[this index]

I still find that a bit hard to read since "do" is also the beginning of a valid expression.
 
A better solution imo is getting rid alltogether with this exceptional syntax for func defs and only keep the regular:

  negate = fn (n Int) - n      ' guess there is no need for "end" here?
  negate = fn (n Int)
      - n
  end

Magpie does have functions like that, but multimethods are not just variables (in fact, they occupy a separate namespace, which may or may not be a good idea).

I'd find the above confusing because "=" *replaces* a variable in other cases, but here it would *merge* two method definitions. It also isn't clear how all flavors of methods would be defined like that:

1. Getters where there is an argument to the left but not to the right:  "string" count
2. "Function-like" method where there is only an argument on the right:  print("hi")
3. Full methods with args on both sides: list add("item")

I guess in programming language design all non absolutely necessary occurrences of alternatives are wrong; if only, because people tend to expect different idioms to have different meanings (esp in computing, unlike in natural languages). Here, the cost is five easy to type characters: " = fn ".

The above syntax is actually a valid way to define a function and assign it to a variable. Right now, Magpie has both functions and methods and the two aren't exactly the same. Functions are first-class and anonymous where methods are always named and not first class. (To make a method first class, you'd wrap it in a function.) There are, I think, reasonable but obscure reasons for the distinction that I can go into if you're interested.

- bob

Alexander Solovyov

unread,
Apr 21, 2011, 4:41:16 PM4/21/11
to magpi...@googlegroups.com
On Thu, Apr 21, 2011 at 17:24, Robert Nystrom <rob...@stuffwithstuff.com> wrote:
> The strictest option would be no default "+" at all, and instead we'd define
> specific methods for each pair we wanted to allow (string+string,
> int+string, string+int, etc.) I'm not sure if the combinatorial explosion
> would be a pain or not though.

Well, I'd personally prefer if it would be (by default, since you can
override anything in your application) as it is in Python [1]: you can
add numbers, concatenate strings and lists (strings with strings and
string with lists), and... that's all? Maybe, hashes and sets, but not
lists and numbers. Just because it makes much more sense to make it
explicit when you want to concatenate string representations of some
objects rather than objects. JS proves that this behavior can make you
cry sometimes. :)

[1]: I think this part is done in a right way in Python

> A lot of this is stuff that still needs to be figured out and stuff that
> really excites me. Part of the reason I started Magpie is because I really
> like API design and was frustrated by what existing languages let me
> express, so it'll be interesting to figure out what good design looks like
> in a language with multimethods.

I completely agree, but in other way if you have all-consuming
concatenation operator, you'll have a lot of strange effects (in other
words: hard to catch bugs) when you're not paying attention enough to
not concatenate objects which can be concatenated only with this
general '+'.

> You can, but the method syntax isn't exactly how I wrote it in the post. The
> current grammar doesn't allow single-line methods, so you have to do:

Ah, ok, no problems then. I just thought they do exist but not in REPL.

> The reason is that getters don't have any separation between the the name
> and the body:
>
> def (n Int) negate - n // how do you tell that "negate" is the name and "-
> n" is the body?
>
> So they require a newline as the separate:
>
> def (n Int) negate
>     - n
> end
>
> So, to be consistent, all methods do. This is one my list of syntax problems
> to fix so if you have any ideas here I'd definitely like to hear them.

Eh, not much. Given that my main language is python, colon comes to
mind, but it doesn't really suit language well (or does it?). But some
separator probably should be invented since single-line functions can
be terribly useful. :))

>> And... maybe I'm blind, but is there any way to see all methods
>> defined on an object? Like dir(something) in Python.
>
> Heh, nope, not yet. Things are still very primitive so there isn't much
> helpful functionality yet. :)

Hehe, it's just convenient method to explore language. Though magpie
is mostly written in magpie, so I just need to pour in sources. :)

--
Alexander

Robert Nystrom

unread,
Apr 21, 2011, 9:34:59 PM4/21/11
to magpi...@googlegroups.com
On Thu, Apr 21, 2011 at 1:41 PM, Alexander Solovyov <alex...@solovyov.net> wrote:
Well, I'd personally prefer if it would be (by default, since you can
override anything in your application) as it is in Python [1]: you can
add numbers, concatenate strings and lists (strings with strings and
string with lists), and... that's all?

That sounds pretty reasonable. What I have now is really just thrown together, so it's all still up in the air. I definitely agree with being more specific with overloads when possible.
 

Eh, not much. Given that my main language is python, colon comes to
mind, but it doesn't really suit language well (or does it?).

Colon is already used for record fields and it may end up getting used for type annotations too at some point, so I'm afraid to overload it more. For a while I was using "->", like:

def (left) + (right) -> left string + right string

That worked OK. Maybe I'll reconsider that at some point. <shrug>
 
But some
separator probably should be invented since single-line functions can
be terribly useful. :))

Agreed on single line methods. :)

- bob

spir

unread,
Apr 22, 2011, 4:33:49 AM4/22/11
to magpi...@googlegroups.com
On 04/21/2011 08:25 PM, Robert Nystrom wrote:

> What about replacing newline by "do"?
>>
>
> Interesting idea:
>
> def (_ IndexableIterator) current do this indexable[this index]
>
> I still find that a bit hard to read since "do" is also the beginning of a
> valid expression.
>
>
>> A better solution imo is getting rid alltogether with this exceptional
>> syntax for func defs and only keep the regular:
>>
>> negate = fn (n Int) - n ' guess there is no need for "end" here?
>> negate = fn (n Int)
>> - n
>> end
>>
>
> Magpie does have functions like that, but multimethods are not just
> variables (in fact, they occupy a separate namespace, which may or may not
> be a good idea).
>
> I'd find the above confusing because "=" *replaces* a variable in other
> cases, but here it would *merge* two method definitions.

Sorry, I'm unsure of syntax but I guess it should have used the "var" prefix"
to introduce a /new/ element:
var somefunc = fn (...) ...

By the way, I take the opportunity to tell that, imo, the distinction between
variable *creation* (with var) and *change* (without it) is a Very Good Thing
(even wrote an article on this topic:
http://spir.wikidot.com/create-vs-change). In my project, the following signs
are used:
n : 1
n := 2
There is a third assignment sign used to replace a referenced thing:
ref := someState ' changes the thing's state
ref :== someThing ' replaces the thing itself

Since, '=' is not an assignment, it is free to compare values by equality; '=='
instead compares things by "unicity" (identity, like python 'is'). A nice
side-effect is the parallel:
x := v --> x = v
x :== t --> x == t

> It also isn't clear
> how all flavors of methods would be defined like that:
>
> 1. Getters where there is an argument to the left but not to the right:
> "string" count
> 2. "Function-like" method where there is only an argument on the right:
> print("hi")
> 3. Full methods with args on both sides: list add("item")
>
> I guess in programming language design all non absolutely necessary
>> occurrences of alternatives are wrong; if only, because people tend to
>> expect different idioms to have different meanings (esp in computing, unlike
>> in natural languages). Here, the cost is five easy to type characters: " =
>> fn ".
>>
>
> The above syntax is actually a valid way to define a function and assign it
> to a variable. Right now, Magpie has both functions and methods and the two
> aren't exactly the same. Functions are first-class and anonymous where
> methods are always named and not first class. (To make a method first class,
> you'd wrap it in a function.) There are, I think, reasonable but obscure
> reasons for the distinction that I can go into if you're interested.

Right, thanks for the explanation. Maybe we come back to this later (when my
exploration of Magpie's world has progressed -- I've not yet reached the OO level).

Denis

spir

unread,
Apr 22, 2011, 5:11:25 AM4/22/11
to magpi...@googlegroups.com
On 04/21/2011 10:41 PM, Alexander Solovyov wrote:
>> So, to be consistent, all methods do. This is one my list of syntax problems
>> > to fix so if you have any ideas here I'd definitely like to hear them.
> Eh, not much. Given that my main language is python, colon comes to
> mind, but it doesn't really suit language well (or does it?). But some
> separator probably should be invented since single-line functions can
> be terribly useful. :))

That's an annoying syntax design problem. I guess since newlines seem to deal
as instruction (statement/expression) separators in Magpie and to separate a
header from its block (at least for func defs), you may need a visible
separator (1) for multi-instruction lines (2) to allow a single-line func body
on the same line as its header.

(2) is annoying especially if the problem happens only for func defs. You'd
need something for func defs playing the role 'then' plays for conditionals.
For regularity, it should also be a key word, not a sign. For regularity too,
it may be good to have it also in multiline func defs! (like then')
Said differently, each block construct should have its "introductory keyword".
(But the same keyword may deal for several constructs, like 'do' for different
loop kinds.)

Another route may be to get rid of such introductory keywords for all
header+block constructs and do it the python way: indented structure instead of
delimited structure. But I guess that's not your style...

Robert Nystrom

unread,
Apr 22, 2011, 2:54:11 PM4/22/11
to magpi...@googlegroups.com
By the way, I take the opportunity to tell that, imo, the distinction between variable *creation* (with var) and *change* (without it) is a Very Good Thing (even wrote an article on this topic: http://spir.wikidot.com/create-vs-change). In my project, the following signs are used:
   n : 1
   n := 2
There is a third assignment sign used to replace a referenced thing:
   ref := someState    ' changes the thing's state
   ref :== someThing   ' replaces the thing itself

Agreed completely. Implicit variable declaration always creeps my out. I want to be able to easily see where a variable comes into existence, and I want to be able to explicitly choose to shadow a variable in an inner scope or not.

Also, when it comes to decorating a variable declaration with additional data (like a type annotation), it's much easier to do that when there's a keyword indicating it. I never liked how C uses a type name to indicate a variable declaration.


Right, thanks for the explanation. Maybe we come back to this later (when my exploration of Magpie's world has progressed -- I've not yet reached the OO level).

Sure, thanks for taking an interest. It's really helpful to have other people to go over this stuff with.

- bob

Robert Nystrom

unread,
Apr 22, 2011, 7:50:03 PM4/22/11
to magpi...@googlegroups.com
On Fri, Apr 22, 2011 at 2:11 AM, spir <denis...@gmail.com> wrote:
On 04/21/2011 10:41 PM, Alexander Solovyov wrote:
So, to be consistent, all methods do. This is one my list of syntax problems
>  to fix so if you have any ideas here I'd definitely like to hear them.
Eh, not much. Given that my main language is python, colon comes to
mind, but it doesn't really suit language well (or does it?). But some
separator probably should be invented since single-line functions can
be terribly useful. :))

That's an annoying syntax design problem. I guess since newlines seem to deal as instruction (statement/expression) separators in Magpie and to separate a header from its block (at least for func defs)

Correct. It's a little more subtle than that. Within a block, newlines separate a sequence of expressions. But the more interesting question is how a block is begun. In Magpie, a newline indicates that too. If it encounters a newline where a single expression is expected (in most places), that indicates the beginning of a block:

if a then b // b is a single expression

if a then // the newline after "then" indicates the start of a block
    b
    c
end

For the most part, I think this syntax works really well. There's very little punctuation and it's pretty clear. There are a couple of edge cases (like def here) where it's a pain.

, you may need a visible separator (1) for multi-instruction lines (2) to allow a single-line func body on the same line as its header.

You can use ";" for that, it's just not common. ";" and "\n" are lexed identically (well, except for line comments, which can't end in a ";" of course).

The problem is that a newline (or a ";") begins a block, but a keyword is still required to end it (usually "end"). So you could do a single-line def like:

  def print(s String) ; blah ; end
 
But that's pretty hideous.


(2) is annoying especially if the problem happens only for func defs. You'd need something for func defs playing the role 'then' plays for conditionals.

Exactly right. I just haven't found a nice keyword that I like. For most cases, the trailing ")" is enough of a separator. This would be fine:

def print(s String) blah

But getters are where the problem appears:

def (_ Vector) magnitude sqrt(x * x + y * y)

Here, it isn't clear that "sqrt" is the beginning of the body of the method.
 
For regularity, it should also be a key word, not a sign. For regularity too, it may be good to have it also in multiline func defs! (like then')

Yes, whatever solution here would likely be used for both. For a while I was using "->":

def (_ Vector) magnitude -> sqrt(...)

Said differently, each block construct should have its "introductory keyword". (But the same keyword may deal for several constructs, like 'do' for different loop kinds.)

Exactly right. That's Magpie's block syntax in a nutshell.
 
Another route may be to get rid of such introductory keywords for all header+block constructs and do it the python way: indented structure instead of delimited structure. But I guess that's not your style...

I think significant indentation works well in statement-oriented languages, but not expression-oriented ones like Magpie. For example:

        fn
            var i = "first"
            var i = "second"
        end shouldThrow(RedefinitionError)
 
Without "end" (or "}" or something else), it gets hard to tell where to put that "shouldThrow".

- bob


Mark Janssen

unread,
Jun 3, 2014, 6:31:24 PM6/3/14
to magpi...@googlegroups.com, rob...@stuffwithstuff.com
 
> "a" + 1
= a1

> [1, 2] + 1
= <List>1

I feel that this basically makes language have weak typing, as
JavaScript does.

That's a good point. All this stuff is brand new, so I'm still working things out. I think the question here is what did the programmer intend when they did "[1, 2] + 1"? If the answer is they wanted to concatenate an item to the list, then we should probably just define:

def (list List) + (item) list add(item)

If the answer is that it was an error, then we should tighten the default "+" to not allow any type. We could do something like:

def (left Stringable) + (right Stringable) concatenate(left string, right string)
 
You should never conflate non-compatible types.  You can't guess what the programmer wanted because you don't know if the programmer intended it or whether it's an error. 

The strictest option would be no default "+" at all, and instead we'd define specific methods for each pair we wanted to allow (string+string, int+string, string+int, etc.) I'm not sure if the combinatorial explosion would be a pain or not though.

A lot of this is stuff that still needs to be figured out and stuff that really excites me. Part of the reason I started Magpie is because I really like API design and was frustrated by what existing languages let me express, so it'll be interesting to figure out what good design looks like in a language with multimethods.

A key idea is whether to allow such "conflation" when there is a common base class (like "Number" when there is an addition between int and float).  But here the question has to be raised, should all classes inherit from some generic "Object" class like Python tried to do?  I argue no.  This is a 30-year dead-end in the OOP world, trying to make "everything is an object" (cf Alan Kay).
 
There must be a deliberate separation maintained between bits (an irreducible, concrete, hardware datum) and Object (extendable, high-level abstractions).  Just like Python has run into, you will unavoidably run into problems as your attempt to abstractify everything fails when you want to do [networking, device drivers, high-speed optimization, textfiles] what-have-you.

Mark
Reply all
Reply to author
Forward
0 new messages