Semantics of Multiple Values

  • Thread starter Kristof Bastiaensen
  • Start date
K

Kristof Bastiaensen

Hi liszt,

I read on Matz' Ruby2.0 slides that the semantics
of Multiple Values will change. Are these already
decided? Here are my ideas about the subject.
I want to post it as a RCR, but I would like to have
your opinion, or criticism first. Also I am not
very familiar with the RCR mechanism.
It is already gotten to a quite large and complicated document.

Thanks,
Kristof

TITLE
Semantics of Multiple Values

ABSTRACT
This RCR describes a possible change in semantics of multiple
values, by making them equivalent to argument passing.

PROBLEM
Currently Array's are used as multiple variables, which
creates some ambiguous or unclear situations.
i.e.: a, b, c = x #where x = [1, 2, 3]

PROPOSAL

* model
=======

This proposal favors the use of multiple values as inherent
to the language, rather than a seperate data-type. It is
based on the observation that returning (multiple) values
is similar to passing arguments. This fact is even more clear
when using continuations.

for example:
x, y, z = mymethod(a, b, c)

def mymethod
return r1, r2, r3
end

would mean this

def mymethod
callcc { |cc| cc.call(2, 3, 4) }
end

#(the following isn't legal syntax, but just to show the meaning)
mymethod(a, b, c) |x, y, z|

basicly the expression:
x, y, z = <expression returning multiple arguments>
will pass the multiple arguments to the given variables like
in function argument passing

The difference with argument passing in functions are the following:
- no new scope is created, bound variable are just replaced
- there is no strict checking if the number of arguments is
correct

* new construct and method
==========================

- new construct:

because Array's are now treated different from multiple arguments
I would like to suggest the following construct
*[a, b] = [1, 2]

meaning the same as
a, b = *[1, 2]
but usefull inside blocks that pass arrays

- new method:

(for now called values)
method returning multiple values as an array

def values(*params)
params
end

values(1, 2, 4) => [1, 2, 4]

* variable assigment examples:
==============================

def multi
return 1, 2, 3
end

#multi may be replaced everywhere with (1, 2, 3)

- variable asignment

x = multi
=> x == 1

x, y = multi
=> x == 1, y == 2

x, y = multi, 3
=> x == 1, y == 3

(x, y), z = multi, 4
=> x == 1, y == 2, z == 4

(*[x, y], z), p = ([1, 2, 3], 4, 5), 6
=> x == 1, y == 2, z == 4, p == 6

* calling to functions
======================

I would recommend the following behavior:

* when passing multiple values from one function to another,
spread the values in a relaxed way (don't check the number
of parameters). When used with an explicit list, use
strict checking.

def func1(a) a end

func1(multi)
=> 1

func1(1, 2, 3)
=> error

def func2(*a) a end

func2(multi)
=> [1, 2, 3]

* when passing nested multiple variables just pass the first
element

def func3(a, b) [a, b] end

func3(multi, 4)
=> [1, 4]

func3((1, 2), 4)
=> [1, 4]
#or maybe signal error? not sure about this one

func2(multi, 4)
=> [1, 4]

func2(*values(multi), 4)
=> [1, 2, 3, 4]

* allow nested multiple values in method definitions

def func4(a, (b, c), d)
[a, b, c, d]
end

func4(1, 2, 3)
=> [1, 2, nil, 3]
#maybe raise an error?

func4(1, (2, 3), 4)
=> [1, 2, 3, 4]

func4(1, multi, 4)
=> [1, 1, 2, 4]

def func5(a, *[b, c], d)

func5(1, 2, 3)
=> [1, 2, nil, 3]

func5(1, [2, 3], 4)
=> [1, 2, 3, 4]

func5(1, (2, 3), 4)
=> [1, 2, nil, 4]
# (what will happen here is only the 2 from (2, 3) will be passed
# to func5, converted into an Array, and passed to b

* Making argument passing and multiple assignment more similar
==============================================================

There may be other features that could be passed from arguments
passing to multiple assignment, for example hash paramaters?
Other may not be appropriate (i.e. blocks).
 
J

Jean-Hugues ROBERT

Multiple Values, Assignments and *Unifications*

This RCR is really nice.
The multiple assignment reminds me of Prolog's "unification" mechanism.
What about some further generalization ?

Why not a generalized "assign" operator ? Syntax:
assign term1, term2 [, term3 [, ...]]
And of course an "unify" operator, Syntax:
unify term1, term2 [, term3 [, ...]]

The difference between "unify" and "assign" is only when
some lvalue exists already. In that case, unify checks that
the previous value is equal to the new value. If not, no assignment
is done at all. Whereas "assign" always assigns.
Another difference is that "assign" is a short match (vs greedy for unify),
i.e. assign [a,b], [1,2,3] # 3 ignored.
i.e. assign [a,b,c], [1,2] # c ignored, not assigned anything, not even nil.

The operator = is kept as it is today,
left-terms = right_term would be equivalent to
assign [left-terms], right-term

Result
def mymethod(); return 1, [2], 3 end # Returns an Array
a, b, c = mymethod # => 3, a == 1, b == [2], c == 3
assign [a,], mymethod # => 2, a == 1, b == 2, c unchanged
assign [a,[],c], mymethod # => false, [] instead of [2]
p unify [a,[2],3], mymethod # => true
p unify r, mymethod # => [1,[2],3]
p unify [a,_,_], mymethod # => 1, _ means "ignore me"

Add tail recursion optimization and you become functional.
I guess that with some callcc(), backtracking could come too.

A key benefit of this proposal is that it does not change
the current semantic of returned values. As a result it is
fully backward compatible with existing source code.

Would this solution solve the issue that your RCR solves ?

Jean-Hugues

BTW: It is often said that operator = is unusal because it does
not apply to an object. It could, we would need a BoundVariable
class, close to a Bindind, but referencing a specific variable.

Hi liszt,

I read on Matz' Ruby2.0 slides that the semantics
of Multiple Values will change. Are these already
decided? Here are my ideas about the subject.
I want to post it as a RCR, but I would like to have
your opinion, or criticism first. Also I am not
very familiar with the RCR mechanism.
It is already gotten to a quite large and complicated document.

Thanks,
Kristof

TITLE
Semantics of Multiple Values

ABSTRACT
This RCR describes a possible change in semantics of multiple
values, by making them equivalent to argument passing.

PROBLEM
Currently Array's are used as multiple variables, which
creates some ambiguous or unclear situations.
i.e.: a, b, c = x #where x = [1, 2, 3]

PROPOSAL

* model
=======

This proposal favors the use of multiple values as inherent
to the language, rather than a seperate data-type. It is
based on the observation that returning (multiple) values
is similar to passing arguments. This fact is even more clear
when using continuations.

for example:
x, y, z = mymethod(a, b, c)

def mymethod
return r1, r2, r3
end

would mean this

def mymethod
callcc { |cc| cc.call(2, 3, 4) }
end

#(the following isn't legal syntax, but just to show the meaning)
mymethod(a, b, c) |x, y, z|

basicly the expression:
x, y, z = <expression returning multiple arguments>
will pass the multiple arguments to the given variables like
in function argument passing

The difference with argument passing in functions are the following:
- no new scope is created, bound variable are just replaced
- there is no strict checking if the number of arguments is
correct

* new construct and method
==========================

- new construct:

because Array's are now treated different from multiple arguments
I would like to suggest the following construct
*[a, b] = [1, 2]

meaning the same as
a, b = *[1, 2]
but usefull inside blocks that pass arrays

- new method:

(for now called values)
method returning multiple values as an array

def values(*params)
params
end

values(1, 2, 4) => [1, 2, 4]

* variable assigment examples:
==============================

def multi
return 1, 2, 3
end

#multi may be replaced everywhere with (1, 2, 3)

- variable asignment

x = multi
=> x == 1

x, y = multi
=> x == 1, y == 2

x, y = multi, 3
=> x == 1, y == 3

(x, y), z = multi, 4
=> x == 1, y == 2, z == 4

(*[x, y], z), p = ([1, 2, 3], 4, 5), 6
=> x == 1, y == 2, z == 4, p == 6

* calling to functions
======================

I would recommend the following behavior:

* when passing multiple values from one function to another,
spread the values in a relaxed way (don't check the number
of parameters). When used with an explicit list, use
strict checking.

def func1(a) a end

func1(multi)
=> 1

func1(1, 2, 3)
=> error

def func2(*a) a end

func2(multi)
=> [1, 2, 3]

* when passing nested multiple variables just pass the first
element

def func3(a, b) [a, b] end

func3(multi, 4)
=> [1, 4]

func3((1, 2), 4)
=> [1, 4]
#or maybe signal error? not sure about this one

func2(multi, 4)
=> [1, 4]

func2(*values(multi), 4)
=> [1, 2, 3, 4]

* allow nested multiple values in method definitions

def func4(a, (b, c), d)
[a, b, c, d]
end

func4(1, 2, 3)
=> [1, 2, nil, 3]
#maybe raise an error?

func4(1, (2, 3), 4)
=> [1, 2, 3, 4]

func4(1, multi, 4)
=> [1, 1, 2, 4]

def func5(a, *[b, c], d)

func5(1, 2, 3)
=> [1, 2, nil, 3]

func5(1, [2, 3], 4)
=> [1, 2, 3, 4]

func5(1, (2, 3), 4)
=> [1, 2, nil, 4]
# (what will happen here is only the 2 from (2, 3) will be passed
# to func5, converted into an Array, and passed to b

* Making argument passing and multiple assignment more similar
==============================================================

There may be other features that could be passed from arguments
passing to multiple assignment, for example hash paramaters?
Other may not be appropriate (i.e. blocks).
 
K

Kristof Bastiaensen

Hi,
this looks very interesting. I particulary like the hash
passing mechanism. I will add it to my RCR. It would
then work also inside method definitions:

def params({:this => a, :that => b})
[a, b]
end
params( {:this => 1, :that => 2, :dummy => 3} )
=> [1, 2]

Maybe it would be too hard to implement, or decrease
performance, but that is not for me to decide!
Examples of possible rules:

{:a=>a, :b=>b} = f(x)
# provided f(x) returns hash, h
# h[:a] and h[:b] assigned to a, b

y.a = f(x)
# provided f(x) returns object x with accessor a
# assign x.a to y.a

This wouldn't work, because y.a = f(x) is already
valid syntax, meaning call y.a= with the value of
f(x)
y.a, y.b = f(x)
# provided f(x) returns object x with accessors a, b
# assign x.a, x,b to y.a, y.b

Neither does this, because it would mean pass the multiple
values from f(x) to the methods y.a= and y.b=
 
K

Kristof Bastiaensen

Hi,

Multiple Values, Assignments and *Unifications*

This RCR is really nice.
The multiple assignment reminds me of Prolog's "unification" mechanism.
What about some further generalization ?

Why not a generalized "assign" operator ? Syntax:
assign term1, term2 [, term3 [, ...]]
And of course an "unify" operator, Syntax:
unify term1, term2 [, term3 [, ...]]

Result
def mymethod(); return 1, [2], 3 end # Returns an Array
a, b, c = mymethod # => 3, a == 1, b == [2], c == 3
assign [a,], mymethod # => 2, a == 1, b == 2, c unchanged
assign [a,[],c], mymethod # => false, [] instead of [2]
p unify [a,[2],3], mymethod # => true
p unify r, mymethod # => [1,[2],3]
p unify [a,_,_], mymethod # => 1, _ means "ignore me"


This looks very interesting, I hadn't thought so far.
(I also don't know so much about Prolog). From the
point of view of syntax, it would be hard to implement,
because assign [a, ], mymethod is already valid syntax,
and would have to make assign and unify a special construct
or operator. This may be not so clear. I cannot say much
more about it since I am not into prolog.
A key benefit of this proposal is that it does not change
the current semantic of returned values. As a result it is
fully backward compatible with existing source code.

The current semantic will change anyway (see also Matz
slides about Ruby 2.0), because it has some ambiguities.
for example:

arr = [1, 2, 3]
a, b, c = arr
#currently a == 1; b == 2; c == 3
#should be a == [1, 2, 3]; b == nil; c == nil
Would this solution solve the issue that your RCR solves ?

I think a unify operator would be a different addition than
what I described in the RCR.
Jean-Hugues

BTW: It is often said that operator = is unusal because it does
not apply to an object. It could, we would need a BoundVariable
class, close to a Binding, but referencing a specific variable.

Yes, I also think that would be nice. Yet another RCR?
 
D

Dan Doel

Hi.

I thought since you said you don't know about Prolog, I'd say something about
it and unification, since it's interesting. Feel free to ignore this, though.

Prolog is a logic programming language, and as such, doesn't have functions
per-se. Instead, it has predicates, which are either true or false. So, for
example:

plus(0, 0, 0).
plus(0, 1, 1).
plus(1, 0, 1).

Are three predicates that are true. The first one means 0 + 0 = 0, the
second means 0 + 1 = 1, and the third means 1 + 0 = 1 (at least, it could
mean that. It could mean many things, really).

Now, with Prolog programs, you can define what's true for constants, like
above, but that doesn't get very interesting. More interesting is defining
what's true for variables. So for example, you can define Pythagorean
triples like so:

ptriple(X, Y, Z) :-
X2 is X*X,
Y2 is Y*Y
Z2 is Z*Z
Z2 is X2 + Y2.

Now, when you write

ptriple(3, 4, 5)?

Prolog unifies X2 with X*X and so on, and finally it checks that
X*X + Y*Y = Z*Z, and finds it's true, so it prints "yes" or something like
that.

However, unification is more powerful than that. With unification, you can
specify variables in your ptriple "call" and Prolog will search for values
that make it true (assuming ptriple is written correctly, which it is). So,
you can do:

ptriple(3, 4, Z).

And it will determine values of Z for which ptriple is true. Similarly, you
can do

ptriple(3, Y, 5).

And it will determine values of Y for which ptriple is true. This is quite a
bit different from all the procedural and functional languages that I
personally know, because in all of those, you have to specify which arguments
are in-parameters and which are out-parameters beforehand, but with Prolog,
if you write your predicates properly, any variable can be an in or out
parameter.

Having similar functionality in Ruby would be interesting. I don't know if
it's feasible, since unification algorithms aren't particularly easy to write,
and I don't know if it would fit with the other aspects of the language. The
proposed unification seems like a subset of the full functionality of
unification anyway, though, so that might be feasible.

The other proposition here is pattern matching, which is also in existing
programming languages. Haskell has pattern matching. For example,
lists can be built like so:

a : list

Where a is an element, list is a list, and ':' is the cons operator. However
when writing list processing routines, you don't need to take a list and
call head and such. What you can do is:

f (x:xs) = ...

And because of Haskell's pattern matching, if you pass something that is a
cons of an item and a list, the item gets assigned to x, and the rest of the
list gets assigned to xs. You can also do more complicated things where you
assign the whole list to a variable, but still assign heads and tails to other
variables and so on. I don't know if Matz would want to take the time to
implement such functionality, but it can be done.

Anyhow, that's it for my language theoretic tangent of the day. If any of this
was interesting, I might suggest learning a little about Prolog or Haskell.
The Art of Prolog is purportedly good (I have it, but haven't gotten around to
reading it yet), and there are several pretty good Haskell tutorials on the
internet.

Have a nice day.

- Dan
 
S

Simon Strandgaard

Dan Doel said:
The other proposition here is pattern matching, which is also in existing
programming languages. Haskell has pattern matching. For example,
lists can be built like so:

a : list

Where a is an element, list is a list, and ':' is the cons operator. However
when writing list processing routines, you don't need to take a list and
call head and such. What you can do is:

f (x:xs) = ...

And because of Haskell's pattern matching, if you pass something that is a
cons of an item and a list, the item gets assigned to x, and the rest of the
list gets assigned to xs. You can also do more complicated things where you
assign the whole list to a variable, but still assign heads and tails to other
variables and so on. I don't know if Matz would want to take the time to
implement such functionality, but it can be done.

Ruby's case statement can do pattern matching.. relatively close to SML, Haskell.

server> ruby a.rb
result=61
server> ruby a.rb
result=61
server> expand -t2 a.rb
def ackerman(m, n)
case
when m==0: n+1
when n==0: ackerman(m-1, 1)
else ackerman(m-1, ackerman(m, n-1))
end
end
result = ackerman(3, 3)
puts "result=#{result}"
server>
 
K

Kristof Bastiaensen

Hi, thanks for the explanations!
Having similar functionality in Ruby would be interesting. I don't know
if it's feasible, since unification algorithms aren't particularly easy
to write, and I don't know if it would fit with the other aspects of the
language. The proposed unification seems like a subset of the full
functionality of unification anyway, though, so that might be feasible.

I don't think it would be feasible. Ruby would have to conclude
from
X2 = Z * Z with X2 == 25
that Z == 5. It cannot have this information, because * as a
method could mean anything.
The other proposition here is pattern matching, which is also in existing
programming languages. Haskell has pattern matching. For example,
lists can be built like so:

a : list

Where a is an element, list is a list, and ':' is the cons operator. However
when writing list processing routines, you don't need to take a list and
call head and such. What you can do is:

f (x:xs) = ...

I would propose something like:
def mymethod(*[a, *b])
[a, b]
end
mymethod [1, 2, 3, 4]
=> [1, [2, 3, 4]]

It would only be a subset of pattern matching, but interesting
on its own.

We could use := as a unify operator, it would be a combination
of comparison and assignment.
I have added these to my RCR.
 
K

Kristof Bastiaensen

Hi, here is my update RCR.
It adds a unify operator := , multiple variables
comparison, and some more goodies :)-)

TITLE

Semantics of Multiple Values

ABSTRACT

This RCR describes a possible change in semantics of multiple values,
by making them equivalent to argument passing.

PROBLEM

Currently Array's are used as multiple variables, which creates some
ambiguous or unclear situations. i.e.:

a, b, c = x #where x = [1, 2, 3]

PROPOSAL

* model
========

This proposal favors the use of multiple values as inherent to the
language, rather than a separate data-type. It is based on the
observation that returning (multiple) values is similar to passing
arguments. This is even more apparent when using continuations.

for example:

def mymethod(a, b)
return a + 1, b + 2, a
end

x, y, z = mymethod(1, 2)

would mean this

def mymethod(a, b)
callcc { |cc| cc.call(a + 1, b + 2, a) }
end

#(the following isn't legal syntax, but just to show the meaning)
mymethod(1, 2) |x, y, z|

basicly the expression:

x, y, z = <expression returning multiple arguments>

will pass the multiple arguments to the given variables like in
function argument passing.

The difference with argument passing in functions are the following:


* no new scope is created, bound variable are just replaced

* there is no strict checking if the number of arguments is correct

* new constructs and method
============================

- new constructs:
------------------
because Array's are now treated different from multiple arguments I
would like to suggest the following construct:

*[a, b] = [1, 2]

meaning the same as

a, b = *[1, 2]

but useful inside blocks that pass arrays. Optionally allow:

*[a, *b] = [1, 2, 3]
=> a == 1, b == [2, 3]

or perhaps with hashes:

*{:a => c, :b => d} = {a: 1, b: 2}
=> c == 1; b == 2

*{:a => c, *b} = {a: 1, b: 2, c: 3}
=> c = 1, b = {:b => 2, :c => 3}

these constructs would work both in argument passing and variable
assignment.

- new method:
--------------
(for now called values) method returning multiple values as an array

def values(*params)
params
end

values(1, 2, 4) => [1, 2, 4]


* variable assigment examples:
===============================

def multi
return 1, 2, 3
end

#multi may be replaced everywhere with (1, 2, 3)

x = multi
=> x == 1

x, y = multi
=> x == 1, y == 2

x, y = multi, 3
=> x == 1, y == 3

(x, y), z = multi, 4
=> x == 1, y == 2, z == 4

(*[x, y], z), p = ([1, 2, 3], 4, 5), 6
=> x == 1, y == 2, z == 4, p == 6

* calling to functions
=======================

I would recommend the following behavior:

* when passing multiple values from one function to another, spread
the values in a relaxed way (don't check the number of parameters).
When used with an explicit list, use strict checking.

def func1(a) a end

func1(multi)
=> 1

func1(1, 2, 3)
=> error

def func2(*a) a end

func2(multi)
=> [1, 2, 3]

def func3(a, b) [a, b] end

func3(multi)
=> [1, 2]

* when passing nested multiple variables just pass the first element

func3(multi, 4)
=> [1, 4]

func3((1, 2), 4)
=> [1, 4]
#or maybe signal error? not sure about this one

func2(multi, 4)
=> [1, 4]

func2(*values(multi), 4)
=> [1, 2, 3, 4]

* allow nested multiple values in method definitions

def func4(a, (b, c), d)
[a, b, c, d]
end

func4(1, 2, 3)
=> [1, 2, nil, 3]
#maybe raise an error?

func4(1, (2, 3), 4)
=> [1, 2, 3, 4]

func4(1, multi, 4)
=> [1, 1, 2, 4]

def func5(a, *[b, c], d)

func5(1, 2, 3)
=> [1, 2, nil, 3]

func5(1, [2, 3], 4)
=> [1, 2, 3, 4]

func5(1, (2, 3), 4)
=> [1, 2, nil, 4]
# (what will happen here is only the 2 from (2, 3) will be passed
# to func5, converted into an Array, and passed to b

* Making argument passing and multiple assignment more similar
===============================================================

There may be other features of multiple assignment that could be taken
from argument passing, for example hash parameters(?). Other may not
be appropriate (i.e. blocks).

example:

def func6
return 4, b: 5, c:6
end

a, b:, **keys = func6
=> a == 4; b == 5; keys == {:c=>6}

* Comparing multiple values
============================

Multiple values may be compared using comparison operators. For
example:

a, b, (c, d), e == 1, 2, (3, 4), 5

would mean

a == 1 && b == 2 && c == 3 && d == 4 && e == 5

with the exception that in the first, all the expressions will be
evaluated. It would return false if the number of arguments to the left
aren't equal to the ones to the right.

It would be also useful to be able to compare with the operator ===, so
multiple values can be compared inside case statements. When
comparing one value with multiple values, return true if at least on
matches.

1 === (1, 2, 3)
=> true
#same as 1 === 1 or 1 === 2 or 1 === 3

case a, b
when 1, 2
puts "is 1 and 2"
when (2, 3), 4
puts "is (2 or 3) and 4"
end

* Unify operator
=================

This section descibes a unify operator, that works like a combination
of comparison and assignment (similar to unify in Prolog). The
expression

a, b, c := multi

would assign the lefthand values, if unbound (or nil?) to the
corresponding righthand value, and if bound (or an immediate value)
compare if they are equal. If one of the comparisons would return false,
no assignment will be done.

examples:

a, b, c := 1, 2
=>true (a == 1, b == 2, c == unbound or nil)

a, b := 1, 3
=>false (a == 1, b == 2, c == unbound or nil)

a, c := 1, 2
=>true (a == 1, b == 2, c == 2)

def amethod
return true, 1, 2,
end
true, x, y := amethod
=>true (x == 1, y == 2)

ANALYSIS

The changes I proposed here provide a consistent way of using multiple
values, by using (mostly) the same semantic model for assignment as for
argument passing, without losing any power to the language. The major
drawback is that programs written using the old semantics may not work
correctly, (but the current behavior will change anyway?). It also
descibes some other extensions to the language that may or may not be
useful.

IMPLEMENTATION

These changes will have to be made to the core of the language. Some
features I described above may be to hard to implement or decrease
performance (nested multiple values in method definitions,
assigning variables from hash elements), and therefor disallowed.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Semantics of Multiple Values (updated RCR)"

|TITLE
|
|Semantics of Multiple Values

Interesting, but I'm afraid this one is too big (and too complex)
change. I'd like to see a language with this semantics though.

|*[a, *b] = [1, 2, 3]
|=> a == 1, b == [2, 3]
|
|or perhaps with hashes:
|
|*{:a => c, :b => d} = {a: 1, b: 2}
|=> c == 1; b == 2
|
|*{:a => c, *b} = {a: 1, b: 2, c: 3}
|=> c = 1, b = {:b => 2, :c => 3}

| func3((1, 2), 4)
| => [1, 4]
| #or maybe signal error? not sure about this one

Making (1,2) as values is not a good idea. Perhaps we should prepare
"array to values" converter (opposite of your "values" method).

| func2(*values(multi), 4)
| => [1, 2, 3, 4]

I'm still against argument splat in the middle of actual argument
list. It's not consistent with formal argument list, where splat is
only allowed at the end.

|There may be other features of multiple assignment that could be taken
|from argument passing, for example hash parameters(?). Other may not
|be appropriate (i.e. blocks).
|
|example:
|
|def func6
| return 4, b: 5, c:6
|end
|
|a, b:, **keys = func6
|=> a == 4; b == 5; keys == {:c=>6}

I just don't feel right about this. Maybe arguments and return values
are different beasts in my brain, even though continuation tells us
their similarity.

|* Comparing multiple values
|* Unify operator

I have to confess I couldn't understand those two proposals, how they
behave, and how they are useful.

matz.
 
K

Kristof Bastiaensen

Hi,

| func3((1, 2), 4)
| => [1, 4]
| #or maybe signal error? not sure about this one

Making (1,2) as values is not a good idea.

Yes, I agree that nesting values would be overcomplicating things,
and is probably too hard to implement.
Perhaps we should prepare
"array to values" converter (opposite of your "values" method).

Isn't * an array to values converter?
(like in method(1, *[2, 3, 4]))?
| func2(*values(multi), 4)
| => [1, 2, 3, 4]

I'm still against argument splat in the middle of actual argument
list. It's not consistent with formal argument list, where splat is
only allowed at the end.

Sorry, I overlooked this one.
|There may be other features of multiple assignment that could be taken
|from argument passing, for example hash parameters(?). Other may not
|be appropriate (i.e. blocks).
|
|example:
|
|def func6
| return 4, b: 5, c:6
|end
|
|a, b:, **keys = func6
|=> a == 4; b == 5; keys == {:c=>6}

I just don't feel right about this. Maybe arguments and return values
are different beasts in my brain, even though continuation tells us
their similarity.

Yes, it is perhaps more proof of concept than really practical.
(Being obliged to choose your variable naming wouldn't surely be
a good idea). Some other constructs may be more practical.
When Arrays are not the same as multiple values, the following
will not work:
a = [[1, 2], [3, 4]]
a.each { |a, b| #something useful here ...
}
that's why my idea was to have to following construct:
a.each { |*[a, b]| #something useful ...
}
and similarly:
*[a, b] = a[1]
|* Comparing multiple values
|* Unify operator

I have to confess I couldn't understand those two proposals, how they
behave, and how they are useful.

matz.

The idea of comparing multiple values would be a kind of
syntactic sugar.
Instead of writing
x == 1 && y == 2
you could write
x, y == 1, 2
(which looks much clearer in my eyes).
In concrete it would evaluate all the expressions, and then
compare them one by one. The first on the left will be compared with
the first on the right, the second with the second, and so on.
When all return true, evaluate to true.

When comparing with ===, one could compare with multiple
values:
if (1, 3, 4..5) === x
<...>
end
that would work like it does in case statements:
case x
when 1, 3, 4..5
<...>
end

About the unify operator, I am not so sure myself it is
really usefull. I just put it here for discussion.

Thanks,
Kristof
 
F

Florian Gross

Yukihiro said:
Hi,
Moin!

| func2(*values(multi), 4)
| => [1, 2, 3, 4]

I'm still against argument splat in the middle of actual argument
list. It's not consistent with formal argument list, where splat is
only allowed at the end.

I think both should be allowed. Both func2(*(values + [4])) manual
argument handling in methods are a stark contrast in ugliness to their
respective end-of-argument-list counterparts.

I also think that all this looks even weirder in Array construction. I
think this case would be a good one to support:

ary = [1, 2, 3]
[:foo, *ary, :bar, *ary, :qux]

However I don't think that there should be support for multiple
splat-arguments in method argument lists. That would feel too unnatural.
This would be a small asymmetry.

Regards,
Florian Gross
 
J

Jean-Hugues ROBERT

|* Comparing multiple values
|* Unify operator

I have to confess I couldn't understand those two proposals, how they
behave, and how they are useful.

matz.

I have some Prolog background and I believe Prolog is the
language where unification is central.

First, I have to say that what I proposed is a "poor man's unification".
Not that its value is low, only it does not turn Ruby into Prolog.

Q. So, what is a "poor man's unify".
A. Well... it is Unification. But without a key aspect of it: Backtracking.

Q. Backtracking ?
A. Backtracking is what happen when a function can return many values,
once a time, like a generator if you wish. So let's forget about that.

What remains is a mechanism that does two things at once:
- comparison
- assignment

Q. You mean, I can have my cake and eat it too ?
A. Cool, isn't it ?

Comparison: ==
We know what it is already. It depends either on the identity of objects
or on their value. Let's skip the easy one.

Comparison by value:
For scalar type, it's easy, "a" is "a" and 1 is 1.
For collection, it's slightly more complex. You compare (note the
recursivity introduced here) each element of the collections.

Q. That's not exactly true, .==() is called.
A. True. Let's assume that the unification algorithm walk the collections
itself, calling some .unify() instead.

So far, so good, known stuff.

Now, Assignment: =
Assignment is about setting the value of a Variable.
Before the assignment, there exists 2 cases:
- The variable exists already. It is "Bound" to something
- The variable does not exit. It is "Free"
After the assignment the variable is bound to some value.

Finally, Comparison + Assignment:
Let's assume that, in addition to a scalar and collection, a
value can be a reference to a variable (not the case in Ruby today).

Q. Then, what happens with comparison ?
A. That's easy:
- For a variable that exists already, you pick it's value and
you use it to do the comparison. "Bound" case.
- For a variable that does not already exists, it is even easier,
you assume that the comparison succeed and, as a side effect,
you "create" the variable with the value it was compared against.

Q. That's it !
A. Yes !

Q. Now, what about the consequences/impacts on Ruby ?

A. First and paramount is that when building a value, in the context
of a "Comparison + Assignment", you cannot proceed as usual. i.e.
unify [a,b], [c,d] is not like [a,b] == [c,d].

Q. OK. How ?
A. When you evaluate the later (==), you build values getting rid of the
variables, i.e. you use the value of the variable in place of the
variable itself.

Whereas with unify, you don't. Instead, whenever there is a variable,
you keep it.

Q. How is that possible ?
A. Well, it is... you need something that does not exists in Ruby so far,
an object that describes a variable. In such a way that, instead
of using the variable's value, you reference the variable itself.

Q. In C, this is &var, the "address of var", similar ?
A. Yes, pretty much the same.

So, if a is 1, b is 2... then
[a,b] == [c,d] is about comparing [1,2] with [3,4] and...
unify [a,b], [c,d] is about [&a,&b] and [&c,&d] (using C's syntax)

That's an additional level of indirection.

Q. OK. Nice. But where is it useful ?
A. Very often ! It is already there in Ruby actually !

Q. No kidding ?
A. No, look.

def f( a, b, c ); ... end
When f is invoked, some unification occur:
unify f's args, [a,b,c]

a, b, c = f()
When assignment with multiple values occur:
unify [a,b,c], f() # Q. Almost... what if f() returns [1,2] ?
# Q. What if a, b or c already exists ?

p = proc( { |a,b,c| ... }
p.call( 1, 2, 3)
When Proc get their parameters:
unify [a,b,c], proc's actual parameters

A. "unify" is there but not exactly with the same semantic:
- Assignment always occur, even if variable is already bound.
- nil is assigned to the variable when there is no better value.
That behaviour is what I call "assign", versus "unify".

"unify" and "assign" work even better if a new type of
value is introduced (in addition to the one introduced already,
the one that is a & reference to a variable).

The new type is "FreeClass", much like TrueClass. That is
a Singleton too.

Q. What is it for ?
A. a = FreeClass.instance() is the way to "free" the "bound"
variable named "a". Shorthand: a = free

Now, some examples, explained.

def test()
unify a, 1
p a # => 1, a was free, now it is bound
a = free
p a # free, a is free again
unify a, 2
p a # => 2, a was free
unify a, 3
p a # => 2, a was bound, unify failed.
a = free
unify [a,b], [] # Fails (evaluate to false)
unify [a,b], [1,2]
p a, b # => 1 2, unify succeeded.
unify [free,b], [1,2] # Succeeds
unify [free,b], [1,3] # Fails
unify [free,b], [1,c] # Succeeds
p c # => 2... it works both ways
a = b = c = free
unify [a,b], [1,2,3]
p a, b # nil nil, unify failed
assign [a,b], [1,2,3]
p a, b # 1, 2, assign is more tolerant
assign [2,b], [1,3]
p b # 2, assign failed, no assignments done
end

I hope that this explanation, way too long, gives you
some idea about how powerful an "assign" and "unify"
operators could be and how close they are to what
already exists.

Nota: *, like in f( *args), makes them even more powerful !

Yours,

Jean-Hugues
 
L

Linus Sellberg

Jean-Hugues ROBERT said:
Q. So, what is a "poor man's unify".
A. Well... it is Unification. But without a key aspect of it: Backtracking.

On the other hand, it would probably be reasonably easy to implement
using continuations if one really wanted to.
 
K

Kristof Bastiaensen

Hi,

"unify" and "assign" work even better if a new type of
value is introduced (in addition to the one introduced already,
the one that is a & reference to a variable).

The new type is "FreeClass", much like TrueClass. That is
a Singleton too.

Q. What is it for ?
A. a = FreeClass.instance() is the way to "free" the "bound"
variable named "a". Shorthand: a = free

This type already exists: NilClass, with as only
instance nil. In fact nil is always used for unbound variables.
Whenever a variable isn't bound, it evaluates to nil, for all
variables (global, object) except local. Local variables are
the only ones that can raise an error, the reason for this is
that local variables and methods or interchangeable. Whenever
Ruby sees a local variable that didn't have an assignment before,
it treats it as a method call. When at runtime it executes the
method, and cannot find it, it will give an error.

I would suggest using := as a unification operator. Ruby could
then always see that what is before the operator are variables.
It would treat nil as the unbound.
I am still not really convinced of the practical use of this
operator. I can see one usage, as initialization:

a := 0 #meaning a = 0 if (a == nil).

perhaps the following

def search_data
some_operation
return succeed, value1, value2
end

a, b = nil
if true, a, b := search_data
<use a, b>
end

#but you may as well use
succes, a, b = search_data
if(succes)
<use a, b>
end

There may be other real world practical uses, but I cannot think
of any.
Any suggestions?
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Semantics of Multiple Values (updated RCR)"

|Isn't * an array to values converter?
|(like in method(1, *[2, 3, 4]))?

I don't think so, even with your proposal. If * (splat operator) is
an array to values converter,

def func1(a,b)
[a,b]
end
func1(1, *[2, 3, 4])

should give [1,2], unless values work like LIST in Perl.

|> |* Comparing multiple values
|> |* Unify operator
|>
|> I have to confess I couldn't understand those two proposals, how they
|> behave, and how they are useful.
|
|The idea of comparing multiple values would be a kind of
|syntactic sugar.
|Instead of writing
| x == 1 && y == 2
|you could write
| x, y == 1, 2
|(which looks much clearer in my eyes).

Not for my eyes. YMMV.
I don't feel right to mix comparison operands x, 1, and y, 2.

matz.
 
J

Jean-Hugues ROBERT

Hi,


This type already exists: NilClass, with as only
instance nil. In fact nil is always used for unbound variables.
Whenever a variable isn't bound, it evaluates to nil, for all
variables (global, object) except local. Local variables are
the only ones that can raise an error, the reason for this is
that local variables and methods or interchangeable. Whenever
Ruby sees a local variable that didn't have an assignment before,
it treats it as a method call. When at runtime it executes the
method, and cannot find it, it will give an error.

nil ? Not quite. nil is a valid value for a bound variable.
unify nil, 1 # fails
I would suggest using := as a unification operator. Ruby could
then always see that what is before the operator are variables.
It would treat nil as the unbound.

Makes sense (but a binary unify operator is more like =~= than
:=, because it works both ways, a := b eqv b := a).

syntax unify x, z makes sense too because one can also unify x, y, z

BTW: what is *after* the operator is also variables (sometimes).
Having the variables on the left side is just a trick to put the
emphasis on the fact that statement is more like assignment than
comparison.
I am still not really convinced of the practical use of this
operator. I can see one usage, as initialization:

inits ? that's right, specially considering that Ruby current
initialization semantic is already a subset of unify.
a := 0 #meaning a = 0 if (a == nil).

No. Should be:
a := 0 # meaning a = 0 if a does not exists already.
I would prefer a =~= 0 however, or unify a, 0
perhaps the following

def search_data
some_operation
return succeed, value1, value2
end

a, b = nil
if true, a, b := search_data
<use a, b>
end

another example:
#but you may as well use
succes, a, b = search_data

That is an example where one can see that something very
similar to a more generalized unify already exists in Ruby.
i.e. success, a, b = seach_data eqv unify [success, a, b], seach_data
I am proposing to enhance slightly what already exists.
if(succes)
<use a, b>
end

Yes, but:
success, a, b = seach_data
if success and a == "a"
<use b>
end
is becoming verbose compared to:
There may be other real world practical uses, but I cannot think
of any.
Any suggestions?

Plenty. Where ever there is a combination of comparison and
assignment, unify (and/or =~=) will be less verbose.


Back to nil & free:
If you want to avoid a FreeClass, at least you need a way to free
bound variables. I suppose that a there is already enough in Ruby
today to:
1) Check if a variable already exists
2) "delete" it

I also suppose it is possible to prototype unify/assign today in
Ruby. I may do it. However, a user level implementation is not
going to look very nice (not mentioning performances). That is
why some support at the syntax level would help. Additionnaly,
a user level unify will not integrate well with the existing
stuff about multiple assignments and parameters passing.

def test()
# unify [a,b], [1,2]
unify [ ref{:a}, ref{:b} ], [1,2]
a = Free
unify ref{:a}, ref{:b}
end

# def mymethod( a, (b, c) ) # but using user level unify
def mymethod( *args )
unify [ref{:a},[ref{:b},ref{:c}]], args
...
end

Have a look at http://onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc
for a definition of a Reference class and a ref{var_name} factory method.

Yours,

Jean-Hugues
 
J

Jean-Hugues ROBERT

Hi,

In message "Re: Semantics of Multiple Values (updated RCR)"

|Isn't * an array to values converter?
|(like in method(1, *[2, 3, 4]))?

I don't think so, even with your proposal. If * (splat operator) is
an array to values converter,

def func1(a,b)
[a,b]
end
func1(1, *[2, 3, 4])

should give [1,2], unless values work like LIST in Perl.

I tried it. It raises a wrong number of argument error (4 for 2).
However:
def func1(a,b,*rest)
[a,b]
end
func1(1, *[2, 3, 4])
do give [1,2]
|> |* Comparing multiple values
|> |* Unify operator
|>
|> I have to confess I couldn't understand those two proposals, how they
|> behave, and how they are useful.
|
|The idea of comparing multiple values would be a kind of
|syntactic sugar.
|Instead of writing
| x == 1 && y == 2
|you could write
| x, y == 1, 2
|(which looks much clearer in my eyes).

Not for my eyes. YMMV.

Not obvious to me either. If feel like I would rather write
if [x, y] == [1, 2] then
OTOH, x, y == 1, 2 may help avoid building 2 Array objects and
speed things up (an optimizing compiler could do that too).
I don't feel right to mix comparison operands x, 1, and y, 2.

matz.

I agree.
I feel like extending the syntax is worthwhile if the added
benefit is really significant.

Rather than allowing x, y == 1, 2, I would prefer a more
general x, y =~= 1, 2 whose semantic is richer due to the
fact that it may end up assigning either x, y or both if
they don't exist already (or if they were unbound/freed).

Yours,

Jean-Hugues
 
J

Jean-Hugues ROBERT

Have a look at http://onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc
for a definition of a Reference class and a ref{var_name} factory method.

I like that code but I am looking for a better looking syntax.
ref{:a} is quite short, but I am looking for something shorter.

Ideally &a, but that is already valid syntax in Ruby for something else.

r{:a} is shorter, but pollutes Kernel space with a global r() method.

Is there an unary operator that can be redefined ?

Yours,

Jean-Hugues
 
G

gabriele renzi

il Wed, 28 Apr 2004 13:35:09 +0900, Jean-Hugues ROBERT
Rather than allowing x, y == 1, 2, I would prefer a more
general x, y =~= 1, 2 whose semantic is richer due to the
fact that it may end up assigning either x, y or both if
they don't exist already (or if they were unbound/freed).

oh, please no more obscure operators.
I think little people here would like this route when in the future we
found ourself using perl6-ish stuff like "@a >>=:=<< @b" :)
 
M

Mark Hubbart

il Wed, 28 Apr 2004 13:35:09 +0900, Jean-Hugues ROBERT


oh, please no more obscure operators.
I think little people here would like this route when in the future we
found ourself using perl6-ish stuff like "@a >>=:=<< @b" :)

Oh, but it would pretty up the language so much!

|
$@\\ .-. //@$
@a >>=:=<< @b
// -~- \\
LL ((z)) JJ
 

Ask a Question

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

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

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top