7 ms·
Fantastic post. The most salient excerpt for me: def filterMap[B,D](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D <: GenTrav
by jashkenas 15y ago
Fantastic post. The most salient excerpt for me:
def filterMap[B,D](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D
def filterMap[B,D <: GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D
def filterMap[B,D <% GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D
def filterMap[B,D[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[B] <: GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[B] <% GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]], ev: D[B] <:< GenTraversableOnce[B]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]], ev: D[B] => GenTraversableOnce[B]): D[B]
...
> The answer to our original question? It turns out none
> of these are correct. In fact, *it is impossible to insert
> a new method that behaves like a normal collection method.*
> This, despite the heavy advertising of enrich my library.
Stuff like this makes think about how, despite all of the problems with using it in libraries, it's lovely that many dynamic languages can be extended in your application with little fuss.
# Ruby.
class Array
def filter_map
...
end
end
// JavaScript.
Array.prototype.filterMap = function() {
...
};
- lisper 15y agoCommon Lisp: (defmethod filter-map (f (a array)) ...)
- soc88 15y agoThat was not the question. It is trivial to do that in Scala for the use case mentioned. The problem is adding it as an "instance" method to a collection and expecting that it is usable by something not being a collection at all.
- lisper 15y agoI have no idea what you mean by "adding it (which "it"?) as an "instance" method to a collection". You can do this: (defmethod filter-map (f (c (eql some-particular-collection))) ...) but I suspect that's not what you meant. I also can't make heads or tails out of "expecting that it (which "it"?) is usable by something not being a collection at all." I'm not even sure that's proper English, let alone semantically meaningful.
- soc88 15y agoMaybe you should try to understand the actual issue first, _before_ claiming that "but it is easy in my pet language" stuff ... Additionally, last time I looked Cl wasn't really statically typed. Has that changed recently?
- lisper 15y agoMaybe if you stated what you think the actual issue is in clear, unambiguous terms instead of being pissy and snarky about it this discussion will not degenerate into chaos. No, CL is not statically typed. And your point would be...?
- soc88 15y agoWell ... maybe just click the link and read the article? It is pretty clear. Ahh, the famous behavior of Lisp fanatics. Tragic, how it is obvious to everyone – except themselves – why no one wants to use their language. > No, CL is not statically typed. And your point would be...? Uh ... what about a) Author complains about the inability of the compiler to prove some property of his code. b) Untyped languages – by definition – don't provide any substantial proving abilities based on types. c) Therefore, you are completely missing the point.
- lisper 15y ago> Author complains about the inability of the compiler to prove some property of his code. No, the author is complaining about the complexity of the language. Maybe you should go back and re-read the article. Start with the title. > Untyped languages Lisp is not untyped. "Not statically typed" is not synonymous with "untyped." > Therefore, you are completely missing the point. Which of us is missing the point remains to be seen.
- a_strange_guy 15y agoWell that is the beauty of it. In Scala methods/functions that come after the dot are privileged. In a language with multiple dispatch they are not. So CL solves the problem without adding more complexity, whereas in Scala you have to extort yourself to shoehorn some functionality after the dot.
- soc88 15y agoCL isn't even statically typed. Everything is easy if you don't expect that the language gives you any useful guarantees.
- dkhenry 15y agoThat is a nice feature of dynamic languages, however you lose strong typing. I think the thing to take away is Scala hasn't gotten the perfect blend of these two yet. You can't make that competly generic map filtering extension yet. However you can make a less portable alternative. So you make you decision on what is more important.
- cgh 15y agoJust to be pedantic, you lose static typing. Ruby (and Python, etc.) are strongly, dynamically typed languages.
- deleted 15y ago[deleted]
- jobeirne 15y ago> however you lose strong typing You lose static typing; strong typing is different: http://en.wikipedia.org/wiki/Strong_typing http://en.wikipedia.org/wiki/Strong_typing
- akeefer 15y agoIn Gosu, it's pretty much the same (though enhancements are statically dispatched and thus subject to a different set of limitations): enhancement MyEnhancement<T> : T[] { function filterMap() { . . . } } Roughly the same in C# with extension methods, though with C# you explicitly import the extension method while in Gosu they're automatically always there (sort of good, sort of bad). Again, not exactly the same as the dynamic language examples, but enhancements/extension methods do allow you to extend existing classes in a reasonable fashion while still being amenable to all the other advantages static typing gives you.
- dcsobral 15y agoBoth you and jashkenas are missing the point. The examples you are giving are also pretty easy to do in Scala. The more complex method he is proposing (filterMap) cannot be done at all in the languages you mention, though he only uses it precisely to get the most complex kind of method that Scala collections offer. But it is also possible, and I just blogged about it here: http://dcsobral.blogspot.com/2012/01/adding-methods-to-scala.html http://dcsobral.blogspot.com/2012/01/adding-methods-to-scala.... But, no, that is not what he wants. He wants to add this method not to the collections, but to something that isn't a collection. Well, Scala can do that too -- it added all the collections methods to String and Array, didn't it? And here comes the twist: he wants to add filterMap not by adding it directly to them, like Scala does. He wants, instead, to go _through_ that code to get at them. With extension methods, the login would be like this: * X adds extension methods to Y * Z adds extension methods to X * Therefore, Z extension methods should be available on Y And, in fact, it is even possible to do that in Scala for many methods, but not for the particular combination he chose, and while still inferring all types.
- hp 15y agoTo me, a big advantage of Scala's "enrichment" over monkey-patching in Ruby or JS is that it isn't global. That is, you have to import the enrichment. Another code module in the program won't be unexpectedly affected by it. In practice, I almost never use monkey-patching in dynamic languages because it's too dangerous. While in Scala there are cases where enrichment won't work, you can always just write a regular function in those cases, and there are lots of cases where enrichment _does_ work...
- rue 15y agoAdding and/or altering functionality at runtime isn't dangerous. Monkeypatching may be, so avoid that. Also, an entirely too-little used idiom (blame Rails programmers): module OverrideSomeMethod def some_method … end end s = SomeClass.new s.extend OverrideSomeMethod s.some_method
- draegtun 15y agoThis is an idiom I use regularly (in Perl, Ruby, Io & Javascript) and come across it often in the Perl world where Moose roles are used. The only downside of this idiom is the extra runtime cost which maybe an issue for Rails?
- draegtun 15y agoSome languages make monkeypatching a lot less dangerous. For eg. in Perl you can use dynamic scoping to localise its effect: { no warnings 'redefine'; local *SomeModule::some_func = sub { say "MONKEYPATCHED!" }; # now everything in this scope that uses or calls SomeModule->some_func # will now use the monkeypatched version } # where has everything else outside this scope remains unaffected In Ruby Refinements earmarked for ruby 2.0 will have something similar: http://www.rubyinside.com/ruby-refinements-an-overview-of-a-new-proposed-ruby-feature-3978.html http://www.rubyinside.com/ruby-refinements-an-overview-of-a-...
- munificent 15y agoC#: public static class EnumerableExtensions { public static IEnumerable<R> FilterMap<T, R>(this IEnumerable<T> list, Func<T, Option<R>> callback) { ... } } There are some methods that need to be part of the class and carried around with the instance so that things like polymorphism work. But many operations work perfectly fine without. By making those lexically scoped, you avoid the problems of monkey-patching and method collisions. Extension methods are fantastic for this. Also, for kicks, Magpie: def (items) filterMap(callback) var result = [] for item in items do match callback(item) case true, mapped then items add(mapped) else nothing end end result end var result = [1, 2, 3, 4, 5, 6] filterMap with if it % 2 == 0 then (true, it * 2) end print(result) // 4, 8, 12 Magpie is dynamically-typed, but methods are lexically-scoped (and are multimethods).
- benmmurphy 15y agoyou have the same problem in C#. you can't take a Foo class and extend it from outside to have all the IEnumerable methods then let it have all the IEnumerable extension methods.
- michaelochurch 15y agoI think it's a good post as well, but filterMap is a contrived example. It comes out of the box: flatMap can be used as a filterMap, because Option[T] is an acceptable substitute (through implicit convernsions) for Iterable[T]. (An option is a collection type; just one with 0 or 1 elements.) The existing collections functions in Scala take you very, very far. If you need to write your own primitives for performance reasons, it can get tricky, but that's really uncommon. Odersky's book (chapter 25) explains how to do that. Collections libraries are hard in Scala because of what you get. Once you define a few simple functions and possibly implicit conversions, you get all 50+ sequence methods "for free", and if you do it right, your map type functions will return collections of the same type (runtime and static) as the original-- without explicit typing. You get a lot of leverage, but you have to work a little bit for it.
- dcsobral 15y agoI'm sorry, but won't these two examples add the method just to arrays? The author is intentionally trying to add a single method that will work for all collections, both scala's and non-scala's, and will always return a collection of the same type as the one the method was called on. Can you show how to easily do that in a dynamic language?
- Dn_Ab 15y agoIn F# this is approximated with: type 'a IEnumerable with member this.filterMap f = [for x in this do if f x <> None then yield (f x).Value] let j = Map([("a",1) ; ("b",2) ]).filterMap(fun kv -> if kv.Value = 1 then Some(kv.Key,kv.Value) else None) |> Map.ofList let n = [1.;2.;4.].filterMap(fun x -> if x % 2. = 0. then Some(x**2.) else None) The extension works with any Sequence (arrays, sequences, maps,etc.) with the caveat that it maps every enumerable to a list. You could also define an extension for a specific type where a broad definition does not make sense. Personally, I lean more functional, I have never run across a need for extending things in this manner.
- jbrechtel 15y agoArrays are not sequences in Scala. They are Java arrays, not Scala sequences.
- Dn_Ab 15y agoYes, I picked this up, and was not making any statement against that. In fact I do not subscribe to the validity of his approach. But I gave a shot at seeing if I could give code that was succinct and matched. The requirement was to add a method that works for all collections, whether platform or language specific, while preserving type. The code I gave is an approximation of a solution - to use a rough analogy: topologically speaking the code matches but loses the geometry. The code I gave works on basically all .NET collections, whether C# or F#, string or tree - as long as they implement the interface - they are matched. That it leverages the existing organization should not count against it. The failing is that although types are preserved it is under a new geometry or structure.
- soc88 15y agoAnd because Ruby devs think it is so great, they try to make it more Scala-like, right? Or what am I missing?