eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
397 stars 62 forks source link

Extension methods #4252

Open CeylonMigrationBot opened 9 years ago

CeylonMigrationBot commented 9 years ago

[@sadmac7000] Most OO languages that don't allow some extremely irresponsible metaprogramming techniques have a sort of semantic inconsistency when acting on standard types.

Consider the following:

String s = ...

How do we obtain modified forms of the string? If we want the reverse of the string, it would be:

s.reversed

If we want a lower case version of the string we can perform:

s.lowercased

But what if we want a ROT13'd version of the string?

myRot13(s)

We of course have to implement myRot13 ourselves, but I think it is legitimate to ask "why is the syntax for acting upon a string different depending on whether the authors of the language thought to include that action or not?"

Ruby solves this by letting methods be added to classes after runtime, a measure that is in no way appropriate for Ceylon, or compatible with its goals. Other languages allow mixins to be created without consulting the original module author, a more feasible approach, but still one that raises complex questions and jeopardizes Ceylon's careful attention to type safety and focus on immutability.

I propose a simpler solution. When a function takes only one argument, the keyword "this" may be used as the name of that argument.


String rot13(String this) {
...
}

When a function is declared in this way, the following two forms are equivalent:


rot13(a);
a.rot13

This is the entirety of the change. No further special behavior is required. This function is still a regular function type.

If we want to have the appearance of defining a method, we can use Ceylon's currying syntax.


String appendReversed(String this)(String other) { ... }

s.appendReversed(s2);

[Migrated from ceylon/ceylon-spec#1146]

CeylonMigrationBot commented 9 years ago

[@Caementum] I like the idea of extension methods but I don't like using 'this' as the parameter. Instead, could we have an annotation of 'extension'?

extension String rot13(String src) { ... }
s.rot13

The rest of that the above would still apply in that the function must have exactly one argument. The currying syntax would still be usable.

extension String appenedReversed(String src)(String other) { ... }
s.appendReversed(s2);
CeylonMigrationBot commented 9 years ago

[@akberc] Cool idea, but it would need to be internal to the module. For example,

CeylonMigrationBot commented 9 years ago

[@sirinath] Maybe you can have extensions similar to what Swift language has: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html

To address @akberc concern we might need to add functionality to modules / packages prevent some imports:

import a. {!myExt, ...}

import all but myExt

CeylonMigrationBot commented 9 years ago

[@sadmac7000] I'm not sure I see @akberc 's point, or at least not how it applies specially to extension methods. We don't share symbols by default, it's worth noting. I don't see why extension methods need behave specially wrt the shared annotation.

luolong commented 8 years ago

There is a paper that covers something very much like this: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf

vietj commented 7 years ago

I'm upvoting this feature that would allow to externalize the plumbing between Vert.x and Ceylon and make it more lighweight both in code but also in the creation and maintenance process of the stack.

We are currently going to go down this road for Groovy, i.e switch from a wrapper based model that contains the code on classes to an extension model with static methods containing the adapting code.

almondtools commented 7 years ago

Have you thought about the features extension methods could provide for deprecation of methods?

I think it is quite natural that an interface gets to small or to large so it must be extended or pruned. Yet this is a problem if one language version supports a method and another does not.

So if we want to deprecate a method in a new API version:

And if we want to introduce a new method in our new API:

I think extension methods are an promising feature to separate deprecated and experimental features from the core API.

gavinking commented 7 years ago

@almondtools But I don't think it really helps much, because:

remove it from the declaring type and put it into an extension method

This is an operation that is neither binary compatible (on the JVM), nor source compatible, in any language that I know of, since extension method must be explicitly imported by the source code that uses them.

Even worse, if the method is formal (or default), you would break every implementation of the type.