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

Scoped object extensions #9991

Open
jmesserly opened this Issue Apr 18, 2013 · 28 comments

Comments

@jmesserly
Copy link
Member

jmesserly commented Apr 18, 2013

In Dart right now, it's painfully difficult to polyfill new features for the HTML nodes without making changes to dart:html itself.

Here's what they do in JavaScript:
https://github.com/toolkitchen/mdv/blob/stable/src/template_element.js

If you include this code into your application, it seamlessly extends a bunch of DOM types, giving you access to new features. It can do this without requiring you to download a new browser or rebuild Chrome yourself.

I don't see how we can do this in Dart. You can't add an instance method to dart:html types without changing the code of dart:html itself. This shuts down a valuable source of prototyping and feedback for the DOM APIs.

JavaScript has a proposal for "scoped object extensions" that is friendly to dynamic typing:
http://wiki.ecmascript.org/doku.php?id=strawman:scoped_object_extensions

The problem for JS is they don't have much urgency. They get by okay with monkeypatching existing types (see my example above). In Dart we don't have monkeypatching, so we need something else.

Note: IMO, this should not be merged into issue #13, because that bug was closed because it asked for statically typed extension methods. Here I want to discuss the above proposal that relies on dynamic typing. At least, if this one is to be WontFix'd, it should be done for a different reason :)

@jmesserly

This comment has been minimized.

Copy link
Member Author

jmesserly commented Apr 18, 2013

BTW, the reason I'm making this request now, is I'm feeling backed into a corner with HTML5 polyfills. The only option I have is to hack on dart:html itself and add @­Experimental APIs directly into it. But IMO this is not a viable long term strategy for web platform features. They need time to bake in their own libraries without being forced to ship in Dartium/Chromium.

@justinfagnani

This comment has been minimized.

Copy link

justinfagnani commented Apr 18, 2013

Similar to John's dilema, I've been recently asked how you could access properties on an HTML element that were added in JS. This could be the polyfills that John mentions, or application or framework code. Right now the only option seems to be to get a hold of a JS interop Proxy to an element (how might be tricky if elements are automatically converted) and then call methods on that. Extension methods, obviously, could be written to use JS interop but appear to add the method directly to the element.

@gbracha

This comment has been minimized.

Copy link
Contributor

gbracha commented Apr 22, 2013

Needs study.


Set owner to @gbracha.
Added Accepted label.

@dgrove

This comment has been minimized.

Copy link
Member

dgrove commented Apr 29, 2013

Added C1 label.

@mraleph

This comment has been minimized.

Copy link
Contributor

mraleph commented Apr 30, 2013

fyi scoped objects extensions harmony proposal is dead.

@munificent

This comment has been minimized.

Copy link
Member

munificent commented Apr 30, 2013

That's true but I believe that's mostly because the author stopped doing JS-related work and not as much to do with the proposal's merits.

@mraleph

This comment has been minimized.

Copy link
Contributor

mraleph commented Apr 30, 2013

Scoped Objects Extensions proposal mixes static properties (lexical scope) and dynamic properties (lookup) together which leads to immense implementation complexity. Especially in JavaScript where everything is extremely dynamic and library functions are specified in terms of [[Get]] and [[Put]] that are affected by this in obscure ways.

I also think that resulting behavior might be extremely confusing.

I honestly think we should not consider adding anything like that to the language.

@DartBot

This comment has been minimized.

Copy link

DartBot commented Apr 30, 2013

This comment was originally written by Jim.J....@gmail.com


Isn't this request roughly the equivalent of adding a method to an javascript object's prototype at runtime? I believe the resulting functionality is equivalent to C#'s extension methods as well. Given that these are both languages that Dart developers are coming from, I don't see the feature itself as being potentially confusing. Unless there's something I'm missing here...if so, please let me know.

I also agree that it provides much needed support for poly filling new browser features, as well as prototyping changes to libraries without affecting the library's entire user base.

@jmesserly

This comment has been minimized.

Copy link
Member Author

jmesserly commented May 21, 2013

fyi -- for my purposes I don't care if it's scoped or not. Rather the ability to extend an existing type. In essence, parity with JS monkeypatching.

@jmesserly

This comment has been minimized.

Copy link
Member Author

jmesserly commented May 21, 2013

@vegorov if it is purely dynamic, does that address your concern?

look, we can't reject this one hand because it's too static and on another because it's too dynamic. That's just admitting we're stuck in a worst-of-both worlds. We should strive for best-of-both-worlds :)

I'm by no means attached to the details of the solution; rather I'm pointing out this is a problem we must solve if we want Dart to be able to make draft web standards available like JavaScript does.

Gilad had an interesting idea that requires no language changes: put noSuchMethod on Node, and have a means of delegation. It might work, assuming we can address the dart2js challenges.

@munificent

This comment has been minimized.

Copy link
Member

munificent commented May 21, 2013

Gilad had an interesting idea that requires no language changes: put noSuchMethod on Node, and have a means of delegation.

Why not on Object?

@gbracha

This comment has been minimized.

Copy link
Contributor

gbracha commented May 21, 2013

John,

What you describe is a very different beast. That goes under the rubric of mirror builders and such.

This bug is about a rather ill defined language feature that is a half dynamic cousin of things like C# extension methods, Haskell type classes and others. The literature is littered with such things: Class Boxes, selector namespaces and other variants.

@DartBot

This comment has been minimized.

Copy link

DartBot commented May 27, 2013

This comment was originally written by ladicek@gmail.com


I'd like to add that there are mainstream-ish languages doing very similar things: refinements in Ruby 2.0 or augmentations in Golo.

@DartBot

This comment has been minimized.

Copy link

DartBot commented May 27, 2013

This comment was originally written by @MarkBennett


From a language user perspective, I appreciate the convenience and safety that scoped object extensions add, however after hearing thoughts like these from Charles Nutter of jRuby fame:

http://blog.headius.com/2012/11/refining-ruby.html

I'm not sure they're worth the complexity it would add to the language unless the rules for handling name collisions were very clear and could all be resolved at compile time.

Would a solution implemented with noSuchMethod make compile time checks possible?

@munificent

This comment has been minimized.

Copy link
Member

munificent commented May 28, 2013

I'm not a Rubyist, so I don't have a solid grasp of Nutter's post, but I believe any scoped-monkey-patch-like proposal for Dart would not suffer from the same problems that refinements have. In particular, he says refinements are available in:

  1. The direct scope, such as the top-level of a script, the body of a class, or the body of a method or block
  2. Classes down-hierarchy from a refined class or module body
  3. Bodies of code run via eval forms that change the "self" of the code, such as module_eval

Dart doesn't have any eval forms, much less ones that rebind self, so (3) isn't a problem. I don't think any proposal we'd want for Dart would support (2) either. That gets you back to simpler (i.e. faster) dispatch mechanics.

@DartBot

This comment has been minimized.

Copy link

DartBot commented May 28, 2013

This comment was originally written by @MarkBennett


Those make sense to me @­rnystrom. I honestly don't have any experience with language implementations or VMs so most of my understanding of this issue comes from what I know of Ruby.

It would be great to be able to use something like this, especially if it didn't add the VM complexity seen in Ruby. Really happy with how you guys have been trying to integrate as many practical refinements to the language without making the VM or spec to complex. It's a hard balance but you seem to be doing a good job.

@DartBot

This comment has been minimized.

Copy link

DartBot commented May 28, 2013

This comment was originally written by ladicek@gmail.com


Yeah, I think that extensions should only be scoped lexically. Heck, I wouldn't even mind to have a special operator for accessing extensions, provided that it's as easy to write and read as a dot. Like colon, for example.

@DartBot

This comment has been minimized.

Copy link

DartBot commented Jun 25, 2013

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


Doesnt the problem get solved by allowing inheritance to libraries. For example you
can have a mdv_html library which inherits code from html but overrides some methods and adds a few more

// mdv_html.dart
library mdv_html extends dart:html;

// Override function,class,variable definitions

// mdv user file
import "package:mdv_html"

@DartBot

This comment has been minimized.

Copy link

DartBot commented Jan 23, 2014

This comment was originally written by @MarkBennett


Not to poke this issue again, but I wanted to share the way Rust is specifying traits, as it's my understanding their successfully able to resolve all method lookups at compile time. Here's the best description of them I was able to find.

http://pcwalton.github.io/blog/2012/08/08/a-gentle-introduction-to-traits-in-rust/

As a practical example @­wycats is able to expose an implementation of additional methods on an integer using this code:

https://github.com/wycats/rust-activesupport/blob/master/dsl.rs

You can then call methods like 1.days().ago(). Perhaps the Rust convention of passing self as the first parameter to these functions helps resolve some of the ambiguity in the implementation, and I understand this isn't the Dart norm when defining instance methods on a class or mixin, but could a convention like this be adopted in the case of traits?

I only continue to raise this issue as a feature like this would be very useful when prototyping new features that may or may not be accepted into the Dart standard libraries.

Thanks again for all the hard work and continuing to have these discussions about the language. Providing a smooth path for prototyping and deprecating language and library features seems like the only element still missing from the Dart ecosystem.

@munificent munificent changed the title scoped object extensions Scoped object extensions Dec 16, 2016

@mikeaustin

This comment has been minimized.

Copy link

mikeaustin commented Jun 5, 2017

I realize this is an old topic, but the same thing has been debated in TypeScript with no solution.
In my [early stage] JavaScript dialect, I implemented extension methods via external methods:

($ = "foo", $.capitalize || _.capitalize).apply($, []);

_.capitalize is a registered function for the 'this' type, so behaves just like a regular method. More details here: https://www.npmjs.com/package/impulse-js

I'm not saying Dart could/should do it that way, but that's how I tackled the problem. It's very straightforward, and as an added bonus, I added partial application if 'this' is undefined:

let evens = _.filter(_.even());
evens([1, 2, 3]) // [1, 3]

@munificent I miss Magpie :)

@munificent

This comment has been minimized.

Copy link
Member

munificent commented Jun 8, 2017

@munificent I miss Magpie :)

Sometimes I do too!

@yjbanov

This comment has been minimized.

Copy link

yjbanov commented Dec 6, 2017

Should the issue description be reworded to take into account the recent developments in the type system as well as include Flutter use-cases? I'd like to have something like this in Flutter too.

For example, we have class CommonFinders, which could be made vastly more useful if users could extend it to add domain-specific finders:

// package:flutter_test
class CommonFinders {
  // ... standard finders ...
}
const CommonFinders find = const CommonFinders._();

// package:flutter_emoji
extend CommonFinders {
  Finder emotion(Emotion emotion) => new _EmojiFinder(emotion);
}

// package:chatapp
import 'package:flutter_test';
import 'package:flutter_emoji';

testWidgets('displays happy face', (tester) async {
  tester.pumpWidget(new ChatApp());
  // Use standard finder
  expect(find.text('Chat App'), findsOneWidget);
  // Use domain-specific one
  expect(find.emotion(Emotion.happy), findsOneWidget);
});

Another example is Flutter's .of pattern, which is used all over the place. It would be great if classes that publish themselves through the BuildContext were discoverable on the BuildContext object itself. This would provide much better IDE code completion feedback than the static method approach used today. Example:

// Today:
build(BuildContext context) {
  // Hmm, what can I access via "context"? /goes to docs.flutter.io
  // 10 minutes later:
  final l10n = MaterialLocalizations.of(context);
  final theme = Theme.of(context);
}

// With extensions:
build(BuildContext context) {
  // Type "context." + CTRL + Space.
  // Gets "localizations", "theme" and other suggestions.
  final l10n = context.localizations;
  final theme = context.theme;
}

To find real-world examples search for ".of(" and "find." in Flutter apps and tests.

@touseefbsb

This comment has been minimized.

Copy link

touseefbsb commented Aug 6, 2018

is there any hope on this feature at all?

@munificent

This comment has been minimized.

Copy link
Member

munificent commented Aug 6, 2018

There is always hope!

Now that Dart has a strong, sound static type system, we have the option of doing statically dispatched extension methods similar to C#, Kotlin, et al. That makes this more feasible than it was before. (The JavaScript "scoped object extensions" proposal never went anywhere, and doing "extension methods" using dynamic dispatch is mostly an unproven research topic at this point. Doing them statically is well-understood.)

@touseefbsb

This comment has been minimized.

Copy link

touseefbsb commented Aug 7, 2018

yeah I am also in favor of statically like in kotlin and c#

@HerrNiklasRaab

This comment has been minimized.

Copy link

HerrNiklasRaab commented Oct 24, 2018

Would be great to have! :)

@marcguilera

This comment has been minimized.

Copy link

marcguilera commented Nov 13, 2018

Is there any hope for this to happen? It would be great for Dart as it is one of my favorite things from Kotlin / C#. It would be a great addition for libs such as RxDart too.

@yjbanov

This comment has been minimized.

Copy link

yjbanov commented Nov 13, 2018

Most of the discussion is happening in the language repo:

dart-lang/language#40
dart-lang/language#41
dart-lang/language#42

@lrhn lrhn added core-l and removed p2-medium labels Dec 6, 2018

@lrhn lrhn added this to Non-Breaking and Complex in Language Enhancement Categories Dec 14, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment