asoffer / Icarus

An experimental general-purpose programming language
Apache License 2.0
9 stars 2 forks source link

Argument dependent lookup proposal. #46

Closed asoffer closed 3 years ago

asoffer commented 3 years ago

Problem

Icarus does not provide anything like class member functions, which makes what would otherwise be a short-named function much longer: Example from C++

std::string s;
s.append("abc")

Equivalent in Icarus

s: str.String
str.append(s, "abc")

The problem is the need to prefix append with str.. C++ and other languages with class member functions effectively use the type as a namespace for functions acting on that type. As Icarus doesn't allow this, it makes a lot of functions more verbose than counterparts in similar languages.

Proposal

Currently there are two ways to call a function: func(a, b) (a, b)'func

The proposal is to allow a'func(b) as well and have all arguments preceding the function contribute to argument-dependent lookup. This would allow us to call s'append("abc") because append would be looked up in the module defining str.String.

A caveat

There is one caveat here that would require addressing. We do not currently allow this interpretation of a'func(b) because it is interpreted as (a'func)(b). In other words, if a: A and b: B, a'func(b) would require func to have signature A -> B -> C, but we would want it to be (A, B) -> C.

Interestingly, there's a nice correspondence between these two function types that ML-style languages take advantage of. We would need to ban overload sets consisting of (A, B) -> C and A -> B -> D as they would be ambiguous. However, if one subscribes to the idea that all overloads in an overload set should do effectively the same thing, then any such overloads should really have been such that one is a curried version of the other. In that case, we just need to find another way to express currying. There's lots of syntactic real-estate for that (needs closures as a language feature). A few examples:

wrhall commented 3 years ago

I don't really have context on when a'f is used, but you could just make that the syntax for currying

Then a'f() is invocation and a'f(b) is calling a's method f with argument b.

While I don't love using / (it is a bit overloaded in the language of Will's cognitive processing), the above wouldn't necessarily be the worst substituting ' with / or , or .

wrhall commented 3 years ago

Comparing with the two methods for invoking f at the top, I wonder if my proposal is:

1) Prefix function invocation is (arg)'function

2) Prefix function currying is arg'function Thus, arg'function should also work to invoke a function. And for multiple arguments one could curry them all and then invoke:

3) a'b'function() (note, this is not guaranteed to be as efficient as just invoking, unless the compiler can elide unnecessary currying)

In general, if one wants to immediately invoke the function, they should prefer

4) (a,b)'function

The bad news with this proposal is that it requires an optimization when currying and immediately invoking with additional args on the right. The other oddity is "what function is actually invoked in (3)?"

asoffer commented 3 years ago

The optimizations aren't particularly an issue. If the double-currying is spelled that way in source it could be elided, but I'm also fine if it's not. Our goals on performance are about letting the user eke out every last drop, not about requiring them to.

My concern though is: Is (a + b)'function curried? In other words, what if we need parentheses for grouping as well?

asoffer commented 3 years ago

requiring parens afterwards for a function call is interesting though and could be a nice way to distinguish a call vs currying. That being said, then a double-curried f(a, b) would be written as (b'(a'f))() which doesn't look great. The parentheses are needed because ' should be left-associative to make call-chaining work.

We could also make a'f(b) be it's own ternary-ish operator separate from ' and give them different associativities.

wrhall commented 3 years ago

You should never need to double curry and call, right?

wrhall commented 3 years ago

But b'(a'f) is still ugly

wrhall commented 3 years ago

So let's see an example of call chaining

a'f()'g()
=> (a'f())'g()
?
wrhall commented 3 years ago

note I don't see why you wouldn't write that as f(a)'g() unless f is a method on a