Open igorbonadio opened 9 years ago
My first suggestion:
let sum = [a: Int, b: Int | a + b]
But there is a problem here... How can I explicitly declare the returning type?
Maybe:
let sum = [a: Int, b: Int | a + b] : Int
Or
let sum : (int, Int) -> (Int) = [a, b | a + b]
where (Int, Int) -> (Int)
is the function type.
And function call could be
sum(1, 2) # => 3
# or
[a: Int, b: Int | a + b](1, 2) # => 3
# or
([a: Int, b: Int | a + b] : Int)(1, 2) # => 3
Changing :
to ->
let sum = [a: Int, b: Int | a + b] -> Int
Or
let sum : (Int, Int) -> (Int) = [a, b | a + b]
where (Int, Int) -> (Int)
is the function type.
Updating function syntax:
let sum = [lhs: Int, rhs: Int | lhs + rhs] -> (Int)
Or
let sum : (Int, Int) -> (Int) = [lhs, rhs | lhs + rhs]
I was think how it will work in map-like functions
vector.map([element: Int | element*element] -> (Int))
vector.map([element | element*element])
And there is a problem here... how can this kind of function be generic... remember, we use named parameters...
class IntVector
def map(function: (Int) -> (Int))
...
function(e) # error...
...
end
end
Marking as opened to #18
As we agreed in issue #13:
class IntVector
def map(function: (Int) -> (Int)) -> (IntVector)
...
function.call(parameters: (e)) # ok!
...
end
end
I was thinking... Do you like ruby-like block syntax? We could use it here:
vector.map do |element|
element*element
end
# or
vector.map { |element| element * element } # but I think we should cut this form.
And to define new anonymous functions, it can be done building a new object:
Proc.new |element|
element*element
end
I will think if we can use something like this.
What do you think?
# vector is a vector of integers
vector.map do |element: Int| -> (Int)
element*element
end
vector.map do |element|
element*element
end
# it is just a sugar syntax
# an anonymous functions is
Function.new do |element: Int| -> (Int)
element*element
end
# so you can do
f = Function.new do |element: Int| -> (Int)
element*element
end
vector.map(function: f)
# the rule is: If the function is the last
# parameter, you can use the sugar syntax
As we decided how to define functions (#13), now we need to decide how to define anonymous functions.
I was thinking in something like:
var sum(lhs, rhs) = [ (lhs: Int, rhs: Int -> result: Int) | result = lhs + rhs]
var sum(lhs, rhs) : (Int, Int) -> (int) = [ (lhs, rhs) -> (result) | result = lhs + rhs]
Both are valid. The first one the type is deduced. The second one the type is explicitly defined.
What do you think?
I like your idea, but I think we can simplify it a bit. There are three elements here, right? The name, the type and the body.
# either
var myFunction(first, second -> third, fourth)
# or
var myFunction(first, second) -> (third, fourth)
I'm cool either way, but I'll use the second one for these examples.
var sum(lhs, rhs) -> (result): (Integer, Integer) -> (Integer) # ok
var sum(lhs, rhs) -> (result) = [...] as (Integer, Integer) -> (Integer) # we don't allow this, right?
#or
def method(function: (Integer, Integer) -> (Integer)) -> ()
#...
end
method([...]) # we know what the method expects, so we know what the function's type is.
# if the function's name tells us what the variables are called:
var sum(lhs, rhs) -> (result): (Integer, Integer) -> (Integer) = [result = lhs + rhs]
# If there's no function name, we have to write it down:
method([(lhs, rhs) -> (result) | result = lhs + rhs])
Otherwise, I'm afraid the first way will start getting too verbose (and repetitive) for a simple sum
function. It's already too long for my taste, but I don't think we can shrink it that much more.
This way, actually, anonymous functions would look a lot like normal functions, which is something I like. If we needed more than one statement, we might be able to do it by simply using multiple lines:
var addAndMultiply(lhs, rhs) -> (sum, product): (Integer, Integer) -> (Integer, Integer) =
[ sum = lhs + rhs
product = lhs + rhs ]
or maybe commas:
var addAndMultiply(lhs, rhs) -> (sum, product): (Integer, Integer) -> (Integer, Integer) = [ sum = lhs + rhs, product = lhs + rhs ]
I still think maybe this is too long, but I guess we can always separate the statement in multiple lines to make it more readable.
// same as above, but with an alternative indentation
var addAndMultiply(lhs, rhs) -> (sum, product):
(Integer, Integer) -> (Integer, Integer) =
[ sum = lhs + rhs
product = lhs + rhs ]
var addAndMultiply(lhs, rhs) -> (sum, product):
(Integer, Integer) -> (Integer, Integer) =
[ sum = lhs + rhs, product = lhs + rhs ]
Personally, I prefer the first way.
I agree. But I prefere the first one (without commas):
var addAndMultiply(lhs, rhs) -> (sum, product):
(Integer, Integer) -> (Integer, Integer) =
[ sum = lhs + rhs
product = lhs + rhs ]
I seems good in high order functions too:
def map(f(a) -> (b): (Integer) -> (Integer), vector: [Integer])
// ...
end
map([b = a*a], [1, 2, 3, 4, 5, 6]) // => [1, 4, 9, 16, 25, 36]
Sounds good to me. Can we close the issue?
I think we can close it.
Just a question: can't we define a simpler way of deduce what will be the types of a lambda? I think that one of the greatest advantages of having lambdas is to simplify the definition of functions when passed as parameters. So, in @vinivendra example:
def map(f(a) -> (b): (Integer) -> (Integer), vector: [Integer])
// ...
end
map([b = a*a], [1, 2, 3, 4, 5, 6]) // => [1, 4, 9, 16, 25, 36]
How do we know [b = a*a]
has type (Integer) -> (Integer)
? And will we check if the function really uses parameter a
and sets return b
in a function application (as with map
)? As far as I know, the name f(a) -> (b)
is to be used inside the method - and an external function could have any names, since it had the same type. So, using "normal" functions:
def square(number: Integer) -> (square: Integer)
square = number * number
end
def map(f(a) -> (b): (Integer) -> (Integer), vector: [Integer])
// ...
end
map(square(number) -> (square), [1, 2, 3, 4, 5, 6]) // => [1, 4, 9, 16, 25, 36]
I think we have to think better about this things before closing this issue.
Yes! You are right @renatocf . We have to discuss it. But I think it works. Let me explain:
1) How do we know [b = a*a]
has type (Integer) -> (Integer)
?
def map(f(a) -> (b): (Integer) -> (Integer), vector: [Integer])
# ...
end
As you can see, map define that f should be of type (Integer) -> (Integer)
. So we can infer it at compile-time.
(scala and haskell do something like this)
2) How can we know the names of the parameters?
Check the following code:
var f(a) -> (b) : (Integer) -> (Integer)
f(a) -> (b) = [b = a * a]
Do you agree that it works? The compiler can infer the type and the name of the parameters. So it can be done in the map
code.
3) How does it work with "normal" functions
Consider the following code:
def square(number: Integer) -> (resp: Integer)
resp = number * number
end
var f(a) -> (b) : (Integer) -> (Integer)
f(a) -> (b) = square
Do you agree that it works too? In this case, a = number and b = resp.
So, again, map
works with "normal" functions.
Ah... I forgot:
map(f(a) -> (b): [b = a*a], vector: [1, 2, 3, 4, 5, 6]) // => [1, 4, 9, 16, 25, 36]
I think there is one thing missing here: How can we call an anonymous function expression?
[sum = a + b](a: 1, b: 2) # => 3
I can see how the compiler can infer the type of a
, b
and the return value. It is easy to see the name of the input parameters... but where is the name of the return value?
Wouldn't it be something like
var x: Int;
(sum: x) = [sum = a + b](a: 1, b: 2);
?
hummmm... And what about the following example:
def mul(lhs: Int, rhs: Int) -> (result: Int)
result = lhs * rhs
end
mul(lhs: 2, rhs: [rhs = a + b](a: 1, b: 2)) #= > 6
Is it correct?
It is correct because, if I remember correctly, when a function has only one return value, its name may be omitted. If there were more return values, I wouldn't know how to pass them along as parameters to another function, be it an anonimous function or not.
Yes, you are correct
var x = sum(a:1, b: 2) # is valid. We don't need to name single return values
But the problem is the name of the return variable... Will we infer it as rhs
because of the name of the parameter of mul
?
I think it is great.
Does everyone agrees with it? If so, I think we can close this one too.
Ah, we need a proposal...
So, if you wanted to get more than one return value from a lambda, it would like this?
def mul(lhs: Int, rhs: Int) -> (result: Int)
result = lhs * rhs
end
mul([lhs = x+x, rhs = x+2](x: 10))
Yeah, looks like it works.
Actually, I think that's not how it would go. Functions that return more than one value actually return a tuple, which we may or may not unpack, right?
def randomInts() -> (one: Integer, two: Integer)
one = 1
two = 2
end
var tuple: (Integer, Integer) = randomInts() # receiving the tuple
var x, y
one: x, two: y = randomInts() # receiving the tuple and unpacking it
In @Kazuo256's case, the function returns a tuple of two Integer
s. Because of that, the mul
method would have to receive a tuple of two Integer
s instead of receiving two Integer
s explicitly, since we decided to disallow automatic unpacking of values.
Your proposal then is to allow for anonymous functions to be valid
(sum: x) = [sum = a + b](a: 1, b: 2);
Is that it?
Yes. I agree with @vinivendra . @Kazuo256 's example should not be valid. First because there is no parameter name, and second because the function returns a tuple.
@vinivendra , check if I understood what you said:
var double(a) -> (b) : (Integer) -> (Integer) = [b = 2*a]
mul(lhs: 2, rhs: [rhs = a + b](a: 1, b: 2))
(sum: x) = [sum = a + b](a: 1, b: 2);
Kind of, I think your second example actually falls into the third category, since we only know the parameter names because we're calling the function immediately. I was thinking something like this:
def apply(function(a)->(b): (Integer)->(Integer)) -> (result: Integer)
result = function(a:2)
end
apply(function(a)->(b):[b = 2*a]) # => 4
When we call apply
, we know it expects a function with a parameter named a
and a return value named b
, so we use those names in our lambda. It's a lot like the first example.
Yes. Ok. It is similar to the first.
So, did we reach a conclusion?
Yeah, I'm ok with this.
As we are thinking that positron should have some functional features, I believe we should discuss anonymous functions.