Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C#-style extension methods #13

Closed
DartBot opened this issue Oct 10, 2011 · 19 comments
Closed

Add C#-style extension methods #13

DartBot opened this issue Oct 10, 2011 · 19 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-not-planned Closed as we don't intend to take action on the reported issue type-enhancement A request for a change that isn't a bug

Comments

@DartBot
Copy link

DartBot commented Oct 10, 2011

This issue was originally filed by jon.rimm...@gmail.com


Unless I'm reading the spec and examples wrong, it looks as if Dart uses the horrific Java approach of providing utility methods that operate on a interface, by putting them as static methods within some class with a name like 'Arrays' or 'Collections'. This is nonsense, especially when Linq and extension methods in C# have demonstrated a far superior approach, and Dart should provide an equivalent mechanism.

An obvious, easy way to add this would be that top level functions can optionally be called on an object using the dot operator, in which case the calling instance is passed as the first function parameter.

E.g. to write a generic first method that operates over an iterator for a supplied predicate:

T first<T>(Iterator<T> iterator, bool predicate(T obj)) {
    while (iterator.hasNext()) {
        if (predicate(iterator.next()) {
            return true;
        }
    }
    return false;
}

This could be called on an instance of an iterator as follows:

var jon = peopleIterator.first((p) => p.name == 'Jon');

Using extension methods that also return iterators, they can then chained together to form fluent expressions:

var fiveOldestJons = peopleInterator.where((p) => p.name == 'Jon').orderBy((p) => p.age).take(5);

It's worrying looking through the language design that you don't seem to looked much beyond JavaScript and Java for your inspiration in Dart. I can't speak for users of other languages, but I strongly doubt C# developers will be particularly impressed by a lot of the Java style anachronisms, and I'd urge you to cast your net a little more widely in general.

@DartBot
Copy link
Author

DartBot commented Oct 10, 2011

This comment was originally written by jon.rimm...@gmail.com


Oh, and obviously I was going to write a 'exists' method example first, then changed it to 'first', but forgot to update the return types. But I'm sure you still get the idea...

@DartBot
Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by jat@google.com


Removed Type-Defect label.
Added Type-Enhancement label.

@DartBot
Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by drfibonacci@google.com


Added Area-Language, Triaged labels.

@DartBot
Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by carstenkl...@yahoo.de


Linq is a language extension, something that is not currently part of the language.

As for the second, there is Isolates that would leverage the processing of the iterator into a separate thread, while you would wait for it to return the value you requested, by your predicate.

Perhaps this is an altogether different programming model as in C# or Java?

@DartBot
Copy link
Author

DartBot commented Oct 12, 2011

This comment was originally written by andreev....@gmail.com


C#-style extension methods are not the same as LINQ.

@gbracha
Copy link
Contributor

gbracha commented Oct 12, 2011

C# extension methods rely on mandatory static typing. That is not an option in Dart.

LINQ like mechanisms can be added to a language by other means. We might consider something in this space down the line.


Added WontFix label.

@DartBot
Copy link
Author

DartBot commented Oct 12, 2011

This comment was originally written by jho1...@gmail.com


Well you could support i when static type is present, c# don't support it on variables of dynamic types either

@floitschG
Copy link
Contributor

Type annotations don't have any influence on the behavior of a program. This is a fundamental property of Dart.

@DartBot
Copy link
Author

DartBot commented Oct 13, 2011

This comment was originally written by jho1965us...@gmail.com


Why would you need anything more than type annotation for extension methods?

Extension methods are just a really nice syntactic sugar

http://www.hanselman.com/blog/HowDoExtensionMethodsWorkAndWhyWasANewCLRNotRequired.aspx

@floitschG
Copy link
Contributor

One of the fundamental properties of Dart is that type-annotations don't have any effect on the behavior of the program. We thus can't use them for extension methods.

@floitschG
Copy link
Contributor

If the transformation is based on the annotated type of the first parameter, then the optional type has an effect on the program. There would be a difference between:
var a = ...
a.foo();
and
ClassName a = ...
a.foo();

One of Dart's fundamental properties is, that type-annotations don't have any effect on the behavior. These two code samples must behave the same way. Therefore extension methods, as implemented in C#, can't be implemented in Dart.

@DartBot
Copy link
Author

DartBot commented Oct 13, 2011

This comment was originally written by jon.r...@gmail.com


Um OK, so don't compile to different behaviour. Just have the compiler make the translation regardless of type, and flag it up as a warning in the static checker if the types don't match.

If you're worried about cases where top level and object bound function names collide, then just have the compiler act as follows...

for call 'a.foo()'
  if there exists top level function 'foo'
    emit 'if (foo.hasFunction('foo') a.foo() else foo(a);'
  else
    emit 'a.foo()';

...or have some kind of TLF targeting operator like a:foo()

The point of this enhancement request was for C# STYLE extension methods, e.g. an equivalent feature not an exact duplication. Obviously there may be requirements for slightly different implementation or usage pattern due to Dart being a different language, it just requires a little bit of thinking about. Given that you released this language and asked for feedback, and here you have some feedback that a number of people are clearly interested in, you seem to be in an awful hurry to mark it WONTFIX.

@DartBot
Copy link
Author

DartBot commented Oct 13, 2011

This comment was originally written by jon.r...@gmail.com


Furthermore, if "LINQ like mechanisms can be added to a language by other means. We might consider something in this space down the line." then why are you closing the bug at all? Why not implement it using those "other means" and use this bug to track it/gauge interest in the feature?

@jmesserly
Copy link

FWIW, there's a ES strawman for how to get the same effect in JS:
http://wiki.ecmascript.org/doku.php?id=strawman:scoped_object_extensions

@DartBot
Copy link
Author

DartBot commented Oct 13, 2011

This comment was originally written by jon.rim...@gmail.com


Deleted my comment from a few hours ago as it was unjustifiably rude and inflammatory. Apologies. Nevertheless, I stand by my opinion that is a perfectly realisable feature and has being prematurely closed.

@gbracha
Copy link
Contributor

gbracha commented Oct 14, 2011

Hi Jon,

Missed your previous comment - just as well, I guess. So let me try and explain why I closed the bug, and what to do about it.

The bug was feature request for C# style extension methods. We can't do that, at least literally. I try and keep feature requests narrowly focused because i have found that they tend to meander, touching on various design options/alternatives and features. It becomes very hard to know where the discussion of a given topic is. And having lots of open bugs that may live for a long time is very hard to manage.

So, if we want "support LINQ style functionality" that would be a separate bug, and one which I would be very supportive of. The question is how to do it. There are different mechanisms by which one might achieve that. It is always good to know what problem you want to solve (in this case, uniform, convenient access to data) rather than what solution you are familiar with/fond of. Specific mechanisms don't always carry over from one language to another.

So feel free to open another issue that is more focused on LINQ and less on mechanism.

@DartBot
Copy link
Author

DartBot commented Oct 14, 2011

This comment was originally written by jon.rimm...@gmail.com


Hi,

Thanks for your response. The important thing is I have to emphasise is, the feature I'm asking for is extension methods, not LINQ. LINQ is built on top of extension methods, but is a different and far more involved feature, that brings in an inline query syntax and translation of expression trees into SQL.

Extension methods are far simpler, and solve a far more generic problem. I would suggesting reading around a little more on C# extension methods to get a better understanding of them, but I will do my best to explain here:

Extension methods are a way of solving a problem that manifests in Java, namely writing library code that can be used to operate on interfaces, particularly collections, in a fluent and convenient way. For example, writing a 'first' method that returns the first value in an iterator (IEnumerator in C#) that fulfils a supplied predicate lambda. Java's (and it seem's Dart's) method of doing this is to have helper classes with names like 'Collections' that contain static methods to operate on instances of their similarly-named interface.

So, our 'first' method in a traditional Java-esque language might look something like this:

public class Iterators {
   public static T first<T>(Iterator<T> iterator, Predicate<T> filter) {
      while (iterator.hasNext()) {
         T next = iterator.next();

         if (filter.apply(next)) {
            return next;
         }
      }

      return null;
   }
}

There can be lots more of these methods to support operations like 'where' (e.g. all values that match a predicate), 'orderBy', to return an iterator ordered by a particular field of the object, 'select' to map all the values of an iterator.

All well and good, but the problem comes when we try and use these methods, even if the language supports lambdas the usage is problematic:

lastName = Iterators.Select(Iterators.orderBy(Iterators.where(dataset.getAllPeople(), (r) => r.getFirstName() == 'Jon'), (r) => r.getAge()), (r) => r.getLastName());

It's verbose, involves a lot of nesting, and it also means the lambdas are split from the name of the operation they apply to.

C# (and Java 8) solved this problem using extension methods. They are syntactic sugar that allows you to declare static methods that can be called on objects as if they belonged to the object itself.

Writing our 'First' method in a C#-esque language wouldn't be much different:

public static class EnumeratorEx {
   public static T First<T>(this IEnumerator<T> enumerator, Predicate<T> predicate) {
      while (enumerator.MoveNext()) {
         if (predicate(enumerator.Current)) {
            return enumerator.Current;
         }
      }
      return default(T);
   }
}

The key is the usage of the 'this' keyword in the method signature, which identifies to the compiled that this is an extension method, intended to extend instances of IEnumerator<T>. While the declaration isn't much different, the usage is:

lastName = dataset.GetAllPeople()
                  .Where(p => p.FirstName == 'Jon'),
                  .OrderBy(p => p.Age)
                  .Select(p => p.LastName)

The syntax is much shorter, cleaner and more fluent and functional. It lets you concentrate on what is happening, rather than how it's happening. It's no surprise that Java is adopting virtually the same mechanisms for Java 8. For JavaScript, a similar solution for collections is provided by UnderscoreJS, although that requires explicitly wrapping and chaining objects using a global constructor function.

The thing is, working with and transforming collections of data is one of the most commonplace and important tasks application developers do. Getting Dart right in this regard is absolutely vital if you want to make it an efficient and enjoyable language to work with. C# extension methods offer the best syntax I've seen for doing this well in an imperative language.

Now, maybe you guys have your own ideas how to achieve this, and will come out with something that will blow all this away. But I worry from what I've read here, for example conflating LINQ with extension methods, that you haven't really thought much about it or grasped how important it is.

You say you want to make a better language than JavaScript. Well, the areas where you need to improve on JavaScript aren't things like inheritance. App developers should rarely need to use inheritance if the runtime framework is good enough. But they will need to manipulate collections, observe object mutations, perform data-binding, and getting the mechanisms right for these things is absolutely vital. Copying what Java did ten years ago is not getting it right.

@gbracha
Copy link
Contributor

gbracha commented Oct 14, 2011

Hi Jon,

I know what extension methods are. I know what they are good for. I also know of several other mechanisms in the programming language literature that attempt to solve related problems. Each one behaves a bit differently. I admit I've never seen one I think is satisfactory. Some of these mechanisms depend on static typing (as in C# or Haskell's type classes) some do not (class boxes, selector namespaces).

Those that rely on types are not an option for Dart. Other schemes are essentially research. We are consciously trying to avoid over-innovating. This is a language that will have large numbers of users, not a testbed for cool language experiments. I've done plenty of that.

On the other hand, if a really good scheme comes along, we wouldn't exclude it. We'd want to get experience with it before rushing into it. As a rule, features do not trivially migrate from one language to another.

Finally, if you think we are copying Java you are sorely mistaken. Likewise if you think we have not thought about these issues.

Anyway, thank you for your input.

@DartBot
Copy link
Author

DartBot commented Oct 14, 2011

This comment was originally written by jon.rimm...@gmail.com


Further to the above, you seem to basing your objections to this bug on the fact that extension methods are directly transferable, in implementation terms, from C# to Dart due to differences in the typing system. This may be, but I still don't quite understand why that makes an extension methods feature request unreasonable.

Extension methods are a language feature, the same as classical inheritance, properties, mixins, traits, interfaces, etc. If a bug asking for LINQ equivalent function would be reasonable, why is this not?

I am not asking for extension methods to be exactly copied from C#, I am asking for the ability to attach additional behaviour to all instances of classes and interfaces and call them using a fluent syntax. That is all. That is what 'extension methods' essentially is: methods that extend an interface or object. If there is some other, more implementation-agnostic terminology that I should use when referring to the ability, then I will happily do so and raise a new bug using it, but nobody has suggested any. This feature is called Extension Methods in C#, and AFAIK it is called Extension Methods in Java 8 as well.

It definitely is not "LINQ", and it would be far more incorrect for me to raise a bug calling it that.

copybara-service bot pushed a commit that referenced this issue Jul 21, 2023
Here's a minimal repro that this CL fixes:

`ui.dart`

```dart
library dart.ui;

import 'dart:ffi';

part 'foo.dart';
```

`foo.dart`

```dart
part of dart.ui;

@Native<Void Function()>(symbol: 'foo_func', isLeaf: true)
external void foo_func();
```

When compiling with `compile_platform.dart` with `--target=dart2wasm`, the following error appears:


```
Unhandled exception:
Verification error: Target=wasm, VerificationStage.afterModularTransformations: Invalid location with target 'wasm' on FunctionNode() (FunctionNode): RangeError (offset): Invalid value: Not in inclusive range 0..56: 91
Context: 'foo_func_$import'.
Node: 'FunctionNode()'.
#0      VerificationErrorListener.reportError (package:kernel/verifier.dart:81:5)
#1      VerifyingVisitor.problem (package:kernel/verifier.dart:222:14)
#2      VerifyingVisitor._getLocation (package:kernel/verifier.dart:1361:7)
#3      VerifyingVisitor._hasLocation (package:kernel/verifier.dart:1370:26)
#4      VerifyingVisitor.getSameLibraryLastSeenTreeNode (package:kernel/verifier.dart:1342:28)
#5      VerifyingVisitor.localContext (package:kernel/verifier.dart:1382:24)
#6      VerifyingVisitor.defaultDartType (package:kernel/verifier.dart:1491:41)
#7      Visitor.visitVoidType (package:kernel/visitor.dart:1309:37)
#8      VoidType.accept (package:kernel/ast.dart:11190:42)
#9      FunctionNode.visitChildren (package:kernel/ast.dart:3919:16)
#10     VerifyingVisitor.visitChildren (package:kernel/verifier.dart:259:10)
#11     VerifyingVisitor.visitWithLocalScope (package:kernel/verifier.dart:266:5)
#12     VerifyingVisitor.visitFunctionNode (package:kernel/verifier.dart:721:5)
#13     FunctionNode.accept (package:kernel/ast.dart:3908:38)
#14     VerifyingVisitor.visitProcedure (package:kernel/verifier.dart:620:19)
#15     Procedure.accept (package:kernel/ast.dart:3311:40)
#16     visitList (package:kernel/ast.dart:14488:14)
#17     Library.visitChildren (package:kernel/ast.dart:591:5)
#18     VerifyingVisitor.visitChildren (package:kernel/verifier.dart:259:10)
#19     VerifyingVisitor.defaultTreeNode (package:kernel/verifier.dart:196:5)
#20     TreeVisitor.visitLibrary (package:kernel/visitor.dart:503:35)
#21     VerifyingVisitor.visitLibrary (package:kernel/verifier.dart:367:11)
#22     Library.accept (package:kernel/ast.dart:577:38)
#23     visitList (package:kernel/ast.dart:14488:14)
#24     Component.visitChildren (package:kernel/ast.dart:14320:5)
#25     VerifyingVisitor.visitChildren (package:kernel/verifier.dart:259:10)
#26     VerifyingVisitor.visitComponent (package:kernel/verifier.dart:342:7)
#27     Component.accept (package:kernel/ast.dart:14313:38)
#28     VerifyingVisitor.check (package:kernel/verifier.dart:171:15)
#29     verifyComponent (package:kernel/verifier.dart:69:20)
...
```

The issue seems to be that after doing this native transformation, the node's `fileUri` references the enclosing library (`ui.dart` above), but the `node.location` references the actual source file (`foo.dart` above) indirectly through `node.fileOffset`.

This ends up being an issue when compiling the platform dill in Google3,   but I didn't look into why `flutter build web --wasm` isn't broken.

Internal bug: b/292172146

Change-Id: I2b8d7d215b2c36354860257ce651d50168e9523d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/315360
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Jia Hao Goh <jiahaog@google.com>
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-not-planned Closed as we don't intend to take action on the reported issue type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

5 participants