8 ms·
For the most part, programming constructs like these split the world into two camps: - People who point out things can be done without them, who largely see th
by blagie 4y ago
For the most part, programming constructs like these split the world into two camps:
- People who point out things can be done without them, who largely see them as useless due to lack of familiarity
- People who've used them, and see critical ways to restructure code to make it cleaner using said constructs
That's been the case for a lot of progress in programming. Python, and ES2015, have done a wonderful job of bringing many previously-academic (including functional) programming constructs to a broader community, together with models like reactive programming.
That's true of about half of the bits of progress in programming. Garbage collection was seen as garbage by C/C++ programmers ("What's the big deal with adding a free() call? Stupid lazy people."). Garbage collections wasn't useful because it omitted free() calls, but because it allowed the design of flows with many exit points (for example, different kinds of exception handling, exiting in the middle of a function, or giving up an object in a half-dozen places an hour later in code where tracking for free() requires building an ad-hoc reference counter or garbage collector).
The place where tail calls are a huge deal is when dealing with deep (or even infinitely deep) tree-like structures:
if condition:
return red(left_child)
else:
return blue(right_child)
I don't mind opt-in versus opt-out versus neither. All the reasons listed for not having them are dumb, though, and have good and easy work-arounds. The major one -- debugging -- it's basically always good enough to just have a list of functions called, without having the whole tree. A 10,000 element stack trace is no help at all. You can, for example, keep the first 20 elements of the stack trace (don't start PTCs unless the stack is of a certain depth), and then still keep a list of functions called:
ipython
webapp.main
webapp.handler
render.make_tree
[PTC: render.red*91001, render.blue*10201]
webapp.callback
I have literally never seen a case where having a list of 100k calls in a stack traces is at all useful for anything.
- PathOfEclipse 4y agoOn a tangent, I've always felt garbage collection, and, more importantly, safe memory management, was important because it allows you to mostly pretend that memory allocation isn't a globally side-effecting operation, as such operations are difficult to reason about. It's the same logic that leads to design choices that don't explicitly use global variables or global state. In reality, all memory allocation is still globally side-effecting, and you'll find that out when your program starts GC spiraling, or consuming more memory than you want it to, but being able to pretend otherwise and mostly get away with it means automatic memory management brings a tangible, measurable productivity multiplier to programming that few, if any, other programming language features can boast of.
- blagie 4y agoI disagree. My experience with codebases with programmers who program like that is that you eventually get memory leaks, some of which are near-impossible to debug or fix. You pay for that kind of thinking down-the-line. Enabling new design patterns == good Not thinking about what happens under the hood == bad Catastrophes are rare, but expensive enough to cost more than thinking things through.
- xigoi 4y agoWhere do memory leaks appear more often, in C, or in Python?
- hajile 4y agoMemory leaks appear in BOTH (specifically, any data structure that accidentally keeps references to stuff will leak memory in python). The biggest difference is that Python won't create the particularly bad/dangerous memory leaks (eg, use after free).
- Jtsummers 4y agoUse after free is not a consequence of memory leaks. A memory leak is, specifically, memory which is still allocated but not referenced. Use after free errors can't happen if you don't free the memory.
- blagie 4y agoMy experience is that Python has *many* more memory leaks than C code. They're less visible since I ran C code on a computer with 32MB of RAM, and I run Python code on a computer with 32GB of RAM, but they're very common in modern Python programs. As another comment pointed out, memory issues in Python are less dangerous too. I'm glad not to worry about buffer overflows in Python.
- tsimionescu 4y agoIn my experience, memory leaks in GC languages are very easy to track down, since the timing for analyzing the heap is excellent. While discovering that you do have a memory leak is not very easy, once you know about it you can easily compare heap snapshots, find the increasing objects, and track down their GC roots, all from the same tool. With Java especially, you can even do this on a running service in production, with minimal downtime. In contrast, it's much harder to track down memory leaks in C, since the runtime has little information about the contents of the heap. You typically end up using valgrind to instrument your code, assuming it can still run fast enough to reproduce the problem. The only exception is Go, which has awful tooling for analyzing memory. For some bizarre reason, it can't even show you the gc roots of an object in memory, even though the GC obviously needs this information to work properly.
- jcranmer 4y agoThe problem with tail calls destroying stack traces isn't with the recurse-for-the-tree functions. It's with code like this: function frobnicate_something(foo, bar) { let baz = antifrobnicate(foo, bar); // Hey, I'm a tail call! return exfoliate_meteor(baz); } With tail calls, you now lose the frobnicate_something stack frame and you just see a call to exfoliate_meteor, which can produce confusing results. This lost stack frame is potentially quite injurious, especially when the function that's lost actually does a serious amount of work. This is the rationale behind the proposal to support tail calls only on explicit scenarios, where the developer opts in to throwing away stack frames.
- gumby 4y agoA tail call is just a goto; using a special syntax for it (`for…` or `while…`) is simply syntactic sugar. Nobody complains about the lack of a stack frame in a for loop.
- ajuc 4y agoGoto can be anything - a function call, a loop, an if clause, exception handler. That's the whole reason we created structured programming - so we don't have to guess what it is THIS time. Tail call optimization is nice, but it has its problems. What's the downside to requiring special syntax for it? It frees the programmer reading the code from determining if it's a tail call every time.
- kreetx 4y agoRight, but optimizing tail calls won't lose that structure. But sure, just using goto is a slippery slope to confusion.
- ajuc 4y agoOptimizing calls using the same syntax as regular ones risk introducing stack overflow without noticing. function rec(x, y, z) { if (something(z)) { return rec(x, y, z-1); } return 1; } Now you need to add 1 to the result. function rec(x, y, z) { if (something(z)) { return rec(x, y, z-1) + 1; } return 1; } Ups, now it fails with stack overflow for big z values. Hope you have good unit tests to catch this. To avoid this problem whenever you modify a possibly recursive function in a language with transparent TCO - you have to look through the code to see if it's using TCO. This is wasted time. And it can be non-trivial if you have recursive functions calling each other and other funny stuff. If instead the language required special syntax for TCO: function rec(x, y, z) { if (something(z)) { return TAIL_CALL rec(x, y, z-1) + 1; } return 1; } This would be a compilation error cause it's not a tail call contrary to the declaration. What is the downside to requiring special syntax?
- quietbritishjim 4y ago> People who point out things can be done without them, who largely see them as useless due to lack of familiarity There's no need to dismiss the those who disagree as just having a lack of familiarity. There is a technical trade off to be made, with pros and cons each way. The only people that are objectively wrong are those that claim the decision is clear cut. The con, in particular, is language complexity. You only have to look at C++ to see that it is possible to include to many features in a language. There's no disputing that tail calls are useful. The question is whether they are so useful that everyone who learns the language has to understand them.
- saagarjha 4y ago> Python, and ES2015, have done a wonderful job of bringing many previously-academic (including functional) programming constructs to a broader community This is kind of amusing to hear considering that Python seems to actively design itself around making typical functional programming constructs difficult and JavaScript has a bunch of weird quirks with how it implements things (you’ve seen the “map” joke perhaps?)
- greymalik 4y agoI program professionally in both languages and in my experience the two are very different when it comes to functional programming. FP is viable in JavaScript, if imperfect. And as the language continues to evolve to better support that paradigm its community continues to embrace it. But Python’s choices seem to be actively antagonistic to FP. I’d like to be able to use a functional approach in Python when it makes sense but it’s too hard, too awkward, too flawed, and too against the grain of the language.
- saagarjha 4y agoI’d agree in the sense that JavaScript gives you poor out-of-the-box support but it definitely can be shaped into something pleasant. Python will make you hate yourself if you try, yes.
- blagie 4y agoThe major thing Python has done is bring major functional design patterns, like maps, reductions, everything in itertools, and made human-friendly versions and syntax for them. Right now, that covers 70% of the stuff I did in Scheme over C/C++ (weighed by volume of use, rather than by feature list). A lot of beginner programmers now use features like closures and decorators, and they're accessible. The remaining 30% is awkward, but usually possible. JavaScript is convoluted.... but after the learning curve, it does functional as well as anything, and better than most other things it does. It certainly does functional better than OO.
- int_19h 4y ago
- edoloughlin 4y agoGarbage collection was seen as garbage by C/C++ programmers ("What's the big deal with adding a free() call? Stupid lazy people.") As a former C++ programmer working on a large code base when/before Java was introduced, garbage collection was seen as expensive and slow, but definitely not a waste of time. I would have given my right arm to be free (pun intended) from the need to manually manage my heap across threads. Adding a free() call was never not a big deal.
- froh 4y agoexactly, it was never about attitude, it's about deterministic real-time. C/C++ are low level systems languages for real-time control applications. In that case garbage collection, a non-deterministic unpredictable stop-the world pause, simply is out. so you have to have a base language without GC. you can use garbage collection via libraries in c/c++ if you don't need deterministic real time. It never was about "not liking" GC or "thinking it's garbage".
- moonchild 4y ago> Garbage collections wasn't useful because it omitted free() calls, but because it allowed the design of flows with many exit points This misses the mark a bit. C++ destructors allow for multiple exits. Design at the level of an individual subroutine is not very important, ultimately. Garbage collection is useful because it enables greater modularity, at the level of full applications and protocols. It does not force details of memory management and ownership into api contracts, leaving them free to be changed. (This, incidentally, is one of the reasons smarter people than I take issue with rust: it forces details of ownership into api contracts, reducing modularity. It is also the reason why reference counting is less modular than tracing gc: it does not deal with cycles, so cyclicality is part of api contracts, and a very subtle part at that.)