7 ms·
My favorite is uniform function call syntax. In several languages (Nim, Koka, D, …), you can always write bar.foo(baz) instead of foo(bar, baz) and vice-versa.
by xigoi 4y ago
My favorite is uniform function call syntax. In several languages (Nim, Koka, D, …), you can always write bar.foo(baz) instead of foo(bar, baz) and vice-versa.
Another one from Nim is the implicit result variable. Instead of having to do this:
func sum(nums: seq[int]): int =
var result = 0
for num in nums:
result += num
return result
you just do this:
func sum(nums: seq[int]): int =
for num in nums:
result += num
It saves so much time and I'm disappointed that more languages don't have it.
- onei 4y agoI think you can also do the second in Go with named returns, e.g. func sum(nums []int) (result int) { for _, n := range nums { result += n } } No clue if it's an idiomatic usage, and named returns always felt a little too magic for me.
- zimpenfish 4y agoIt's kinda fine if it's a short function like that[1]. When you get above 10-15 lines in a function though, it's easy to lose track of what's a return variable and what isn't. [1] and everything in the codebase uses that style otherwise it's annoying to have to context-switch every 5 minutes.
- mikepurvis 4y agoLots of languages will implicitly return the final expression— I feel like that's a decent compromise. Not quite as magical as an actual named variable that just exists, but not as clunky as needing as explicit `return` every time.
- tsimionescu 4y agoHonestly, I don't like that style for languages which allow multiple side-effects as well. For example, (progn (print "something") 0) Is honestly pretty ugly from my point of view.
- mikepurvis 4y agoSure, definitely, and in a case like that (say, in rust), I would just put an explicit `return` in. But there are lots of other scenarios where the result is naturally being returned by the final expression and it's quite convenient to elide the extra keyword.
- Falkon1313 4y agoI always disliked implicit returns, and over the years and having dealt with many more codebases, some quite large, I've learned to dislike any implicitness. I would much prefer that you must explicitly return a value (even if it's through an implicitly declared 'Result' variable) rather than just 'try to guess what happened here, in this long function with lots of expressions'. There are a few exceptions, like Forth, where you really have to keep the current state of the stack in mind at all times anyway. Those exceptions naturally tend toward very small functions. Most languages don't, and the result is inevitably difficult to understand bugs.
- ilyt 4y agoyou can do func sum(nums []int) (result int) { for _, n := range nums { result += n } return } and it will work same as if you would do return result As for omitting return entirely I hated it in every language where I saw it. It just feels wrong to not have return in functions that return stuff
- jdmichal 4y agoI think missing `return` works if the language is designed around everything being expressions, so the function is just written as a single expression. I agree that in procedural type paradigms I like having the `return` keyword over things like "return the result of the last expression".
- int_19h 4y agoThis case is a bit different - omitting the return simply returns the implicitly declared result variable, which makes perfect sense.
- michaelsbradley 4y agoThere are some cons to using `result`: https://status-im.github.io/nim-style-guide/language.result.html https://status-im.github.io/nim-style-guide/language.result....
- barrkel 4y agoImplicit result var is also in Delphi, as Result. The original Pascal is to assign to a variable with the same name as the function, which looks kind of odd, and is overloaded in recursive scenarios, since functions with no args don't need parens to invoke.
- KMag 4y agoThe last I used Matlab, it also required you to assign your return value to a variable with the same name as the function. Presumably Gnu Octave also has the same feature.
- tremon 4y agoand vice-versa Doesn't the reverse pollute the function namespace? If every obj.fun() can be written as fun(obj), doesn't that cause ambiguity with a previously imported global function fun()?
- AnimalMuppet 4y agoThat sounds like no fun() at all. Right, I'll show myself out...
- __jem 4y agoIn Rust, it's still qualified by the type you're calling it on, which I assume is also the case in other languages.
- masklinn 4y ago> which I assume is also the case in other languages. Nope. Rust has an extremely restrictive form of UFCS. In fact officially it's not called UFCS, but "Fully Qualified Path syntax".
- masklinn 4y ago> doesn't that cause ambiguity with a previously imported global function fun()? Yes, although overloading can mitigate the issue.
- elcritch 4y agoYah I ran into that issue more in Julia. There's a built-in function to help find clashing functions. In Nim however I've only had it happen a handful of times in a few years. Then you just need to use the module name to qualify it, or change your imports.
- Yujf 4y agoIn rust you can do this but they are namespaced with the name of the struct. So you can do Object::fun(obj)
- andix 4y agoI’m not so sure about that. Did you ever check out extension methods in C#, they are a bit like what you’re describing, but not so radical.
- IIsi50MHz 4y ago> My favorite is uniform function call syntax. In several languages (Nim, Koka, D, …), you can always write bar.foo(baz) instead of foo(bar, baz) and vice-versa. To me, these are "Tell bar's foo to do something with baz." and "Tell foo to do something with bar and baz.". So being 'able' to flipflop the syntax is at least temporarily semantic'ly confusing.
- gernb 4y agoThat arguably depends on your POV. Thinking like python where a method always declares 'self' as the first argument, then a function is just 1 thing (there's no such thing as a method). Then dot syntax is just syntactic sugar for passing the first argument, and there's nothing special about functions. You can manually pass the first argument. In other words, to me it's simpler and therefore less confusing.
- xigoi 4y agoI find the distinction between “foo” and “bar's foo” unnecessarily confusing. For example in C++, why is getting the last element of a “vector” something that belongs to it, but reversing a “vector” is something external?
- SebastianKra 4y agoExcept I find the concept of state "doing something with" other state unhelpful. Why is bar working with baz and not baz working with bar? I struggle with this in Unity: Player.collect(PickUp) // this? PickUp.boost(Player) // ...or this? Instead, the code should describe the interaction between the two units of state: onCollide(Player, Pickup) If we structure our code like in the last example, it makes sense to weaken the `a.b` vs `b(a)` distinction, and instead use the dot as a kind of pipe-operator.
- tsimionescu 4y agoIt shouldn't be that confusing. Ideally, a method should be defined on an object only if it needs to access the encapsulated state of that object. Otherwise, it should probably be a free function. So, in your case, depending on other modeling decisions, I could argue either for onCollide(Player, Pickup) - if Player and Pickup are both plain data, and don't need to guarantee any invariants Player.collect(Pickup) - if Player actually has to ensure some invariants such as health<100) I don't see any good arguments for pickup.boost(Player), in typical games. Of course, if both the Pickup and the Player have some invariants that need to be maintained on a collision, then arguably the design has to be changed at a deeper level.
- throwaway4good 4y agoInitializing at what type and value?
- xigoi 4y agoThe procedure's return type and its default (zero) value.
- int_19h 4y agoFWIW the implicit result variable is as old as FORTRAN and ALGOL, although the common practice then was to name it the same as the function. Delphi is one language that inherited that (via Pascal) but renamed it to Result, although I don't know whether it originated there, or whether Nim picked it up from Delphi.
- tsimionescu 4y agoI've only seen this proposed as a feature to help with template meta-programming (though it could also make similar sense in any dynamic language). There, it helps if a template can say `t.foo()`, and, with UFCS, can use that template with any t for which either `foo(t)` or `t.foo()` exist. In contrast, in C++ today, a template using `t.foo()` limits its own use only to types that have a foo() method, probably unnecessarily (note that writing `foo(t)` in a C++ template is less limiting, as someone who controls neither the template nor the type of t can still define that function). However, outside of this use case, I think conflating these two is more of a negative than a positive. It means that there are twice as many places where I may need to lookup the definition of foo(), at the very least. So I wouldn't add this to any static language that doesn't support templates or macros.
- gpderetta 4y agoI think not introducing UFCSs originally in C++ is one of Stroustrup biggest regrets.
- xigoi 4y agoWhat do you mean by “twice as many places”? And looking up definitions is a job for the language server.
- tsimionescu 4y agoWithout UFCS, if I see obj.foo(), I know I can lookup the definition of foo() in the definition of obj's type, or in its supertypes. Even for foo(obj), there is often a canonical place where such functions are defined. With UFCS, I need to look in both of these places until I can find the right definition. And sure, the IDE/language server/other tooling can often help, but not always (e.g. if I'm browsing some code on Github). Either way, more ambiguity for no gains is typically not a good idea, even if the downsides are minor (again, I am very much in favor of UFCS where it's directly useful, such as C++ or D).
- xigoi 4y agoIn languages that have UFCS, there's no such thing as classes, so… you simply look for the procedure.
- mattpallissard 4y agoI don't like they implicit result variable from a scope point of view.
- xigoi 4y agoWhat do you mean?