4 ms·
> things like `.append` changing the object, returning `None` instead of creating a copy and returning that The obvious question is why it can't return a refer
by Alex3917 5y ago
> things like `.append` changing the object, returning `None` instead of creating a copy and returning that
The obvious question is why it can't return a reference to the list instead of returning None. I feel like if I've been using the language on an almost daily basis for ten years now and I still get burned by that all the time, then it's just a poorly designed feature.
- canjobear 5y agoThe advantage of mutating operations always returning None is that you can easily tell whether a mutation is happening by looking at the code. If you see y = f(x) that means x is unchanged, whereas if you see just f(x) on a line that means something stateful is happening.
- macintux 5y agoThere are counter-examples: functions that return values while also having side effects, cases where the developer simply made a mistake. Coming from a language with baked-in immutability, Python’s behavior in this regard was very difficult to get used to.
- brundolf 5y agoAgreed. JavaScript's Array.sort is an example of this. Most of JavaScript's other array methods return a new array and people get used to chaining them, but sort mutates the array and also returns a reference to it. You can actually get pretty far before being bitten by this so long as you're sorting already-copied arrays. But then one day you hit a bizarre bug caused by behavior that's been sneaking past your radar the whole time.
- Xavdidtheshadow 5y agoI really like ruby's method naming convention for this: * `sort(arr)` returns a sorted copy of the input * `sort!(arr)` returns the sorted original methods that return booleans end in `?`, like `arr.sorted?` It's just a convention, but it's a nice way to let the writer know what will happen.
- dmurray 5y agoI'll just point out that this is originally from Scheme (I think... Maybe Scheme got it from a previous Lisp) but borrowed by Ruby. Neither Scheme nor Ruby do a perfect job with sticking to the naming convention, at least if we include popular libraries, but it is very handy and intuitive. https://stackoverflow.com/a/612588 https://stackoverflow.com/a/612588
- canjobear 5y agoThe ! convention is ok but I don't think it's optimal because, in the presence of higher-order functions and related concepts, it's often not clear if a function should be marked as !. For example if I have a map function that applies a function f to a sequence, should I call it map! because I might pass in a function f that mutates the input? If so then it seems like any function that takes a function as input, or any function that might call a method on an object, should get marked with ! just in case. But if I don't mark it that way then the ! marking is not as informative: I might end up with a line consisting only of non-! functions which still mutates the input.
- zarzavat 5y agomap! would mean a function that performs a map in-place on the array by replacing the values. So it would depend on if the callback was encouraged to mutate the array or discouraged from doing so.
- djur 5y agoArray#map! actually exists in Ruby, too. It mutates the array item by item. Enumerable#each doesn't have a bang, because it doesn't change the enumerable itself, even though it can mutate the objects contained in the enumerable. This is overall consistent -- there's a distinction between mutating the receiving object and mutating objects contained in or referred to by the receiving object.
- dragonwriter 5y agoNote that “!” in Ruby doesn’t conventionally mean “modifies the receiver”, it means “does something that is similar but less likely to be safe than a method with the same name without ‘!’”. A very common case of this is mutating versions of non-mutating methods, but (1) mutating methods (in stdlib or other idiomatic code bases) that have no non-mutating equivalent are not named with a “!”, and (2) methods are sometimes named with “!” because they do dangerous things compared to a base method that are not mutating the receiver.
- aftbit 5y agorandom.shuffle() has bitten me that way a few times too: array = random.shuffle(array) because I expected it to return a copy or reference, instead making my array None. It would also enable chaining operations: array = array.append(A).append(B).sort() In-place vs immutable copy is a language design choice with tradeoffs on both sides, but there's no reason that I can see to not return a reference to the list. Perhaps recognizing this is really the job of an external linter. Sometimes I wonder if the future of enforcing canonical formatting on save like "gofmt" or "black" will extend to auto-correcting certain goofy errors on each save. mypy would yell at you about this, but afaik type-checked python still isn't the norm.
- int_19h 5y agoIn Python, a function that makes and returns a copy would be idiomatically named shuffled(). Consider sorting: xs.sort() # in-place ys = sorted(xs) # copy As for functions returning the object - I think it's a hack around the absence of direct support for such repetition in the language itself. E.g. in Object Pascal, you'd write: with array do begin append(A); append(B); sort; end; Or better yet, in Smalltalk: array append(A); append(B); sort. https://en.wikipedia.org/wiki/Method_cascading https://en.wikipedia.org/wiki/Method_cascading
- igouy 5y agoAlso https://dart.dev/guides/language/language-tour#cascade-notation https://dart.dev/guides/language/language-tour#cascade-notat...