4 ms·
> If you read the article referenced by that thread you'll see that a major issue with string copying is due to having a lot of bad interfaces taking raw C poin
by rbehrends 10y ago
> If you read the article referenced by that thread you'll see that a major issue with string copying is due to having a lot of bad interfaces taking raw C pointers so code at both side of the ibterface need to make copies exactly because the raw C pointer doesn't guarantee exclusive ownership. The remaining issues are due to a badly optimized string buider in Chrome and failure of pre-reserving vector memory which lead to many copies on resize. This last issue is fixed with move semantics in c++11.
The point is that something like:
List.filter (fun s -> String.length s > 0) list
simply cannot be done efficiently, because you require either copying or shared ownership for the strings. This also occurs naturally in a number of other situations, such as storing strings in objects.
> Std::remove_if works genrically on any range. Boost (and the range TR) provides iterator views when you need lazy evaluation.
std::remove_if is destructive. Iterators are not the same thing as a functional filter operation.
> I'm sure you can, in the end you can implement anything manually.
The point here is that a transactional system is more powerful than RAII.
> Only if you use shared ownership. Otherwise is completely predictable.
If you don't use shared ownership, then you're basically limited to lexical scoping.
- gpderetta 10y ago> The point here is that a transactional system is more powerful than RAII my point was that RAII trivially maps to transactions (destructors do rollback and commit is explicit). Transaction RAII object can be composed to make more complex transactions. > If you don't use shared ownership, then you're basically limited to lexical scoping. why would you say that? A common use case is having objects manually removed from collections triggering cleanup actions, like closing sockets, automatically de-registering from event notifications, sending shutdown events, rolling back transactions. That has nothing to do with lexical scoping.
- rbehrends 10y ago> my point was that RAII trivially maps to transactions (destructors do rollback and commit is explicit). Transaction RAII object can be composed to make more complex transactions. RAII is limited in that it's tied to object lifetime, whereas more general transactional semantics can be linked to more general semantic conditions. Also, RAII does not have a good way to distinguish between commits and aborts. > why would you say that? A common use case is having objects manually removed from collections triggering cleanup actions, like closing sockets, automatically de-registering from event notifications, sending shutdown events, rolling back transactions. That has nothing to do with lexical scoping. If you use an explicit action to trigger destruction, then this doesn't have to be a deletion. In fact, having it tied to object destruction is unnecessarily limiting. The general rationale for having RAII is that it happens automagically; if explicit disposal is needed, then much of that rationale goes away. In general, it seems to be me that you don't have actual experience with resource management outside of C++, so you're mostly speculating about what it's like and try to force your thinking about it into a C++-like model.
- gpderetta 10y ago> in general, it seems to be me that you don't have actual experience with resource management outside of C++, so you're mostly speculating about what it's like and try to force your thinking about it into a C++-like model. yes, I'm a C++ programmer. I have experience with resource management in C# and python for example, which are a pale shadow of what is possible in C++. I know nothing of resource management in functional languages, especially regarding transactions and I would love to read more about it if you have some pointers (ah!)
- rbehrends 10y agoThere's an interesting example of resource management in Haskell with monads [1], but it's probably not easy to follow if you aren't already steeped in Haskell lore, so let me be a bit more basic. First, note also that it would not be particularly hard to add RAII on top of a garbage-collected language to coexist with GC for resource management; it's just not done in practice. And it's not because language designers are ignorant of it (Bjarne Stroustrop's Design and Evolution of C++ is part of the standard recommended reading list in the field). Generally, you want resource usage to be a provable property of a program. Not that you'd actually write a formal proof, but you generally want to be able to explain at least informally why resource usage follows certain constraints (e.g. having certain upper bounds). The basic insight that you need is that resource lifetime is just another semantic property that you can handle with basically the same techniques as other properties of programs; you do not need special language support for it (though, obviously, it helps if your language is a bit more expressive than a Turing machine :) ). This means that you'll generally tie resource usage to program state and program behavior that you can reason about. The incidental semantics of object lifetime can be dangerous, especially in a functional language, as object lifetime can sometimes be unpredictable. One of the major hiccups are closures. Closures capture their environment (including local variables) and if they survive the stack frame that created them (because they are returned or stored on the heap), then the lifetime of any captured object can be extended in a fairly unpredictable fashion. Obviously, that is not a good thing, as you have a hard time proving lifetime properties, but few functional programmers would limit themselves to a more trivialized use of closures just for the sake of RAII. Instead, as I said, you tie resource management to program behavior or state. In the most simple case, that can be scoped resource management. But it can also be an LRU cache, a system based on transactions, or something else entirely. Here's a simple example of a library I sometimes use in OCaml: class example = object inherit Tx.resource initializer print_endline "create" method release = print_endline "close" end let _ = Tx.scoped (fun () -> new example) This is a simple lexically scoped transaction, but the library also allows for chained, nested, etc. transactions that don't begin and end based on lexical scope, but (say) program events (e.g. terminate a transaction when a socket is closed from the outside and release resources that are associated with that connection). It can also distinguish between commit and abort behavior (similar to the Haskell example above), will properly error if resource creation is not done within the context of a transaction, plus a few other bells and whistles. [1] https://aherrmann.github.io/programming/2016/01/04/resource-management-in-haskell/ https://aherrmann.github.io/programming/2016/01/04/resource-...
- charlieflowers 10y ago>> The point is that something like: List.filter (fun s -> String.length s > 0) list simply cannot be done efficiently, because you require either copying or shared ownership for the strings. This also occurs naturally in a number of other situations, such as storing strings in objects. This is a very salient point. It bounced around in my brain a couple of hours before I came back to comment. How often do you need to control memory layout and management so you get the absolute best performance? Compare that to how often you need to express filters. For me, there's no doubt that expressing functional logic and having it be decently efficient is the most important need. This little example of yours illustrates that a well-designed garbage-collected language has a HUGE advantage over RAII. I may be slow on the uptake, but this is the first time I've seen it that way.
- gpderetta 10y agoFilters are used all the time in C++. * If the filtered result is used locally in a function (and then thrown away), a filtered view works just fine, is very cheap, efficient and is lazy (nice if you only consume a subset of it). * Often only the filtered view is used, so you can destructively modify the original list, so no copies. * If you need both the original list and the the filtered list, in a functional language you need to allocate new cons cells anyway. The cost of allocating, modifying and touching the new memory is going to dominate except for very long strings, so copying is not an issue. Of course in C++ lists are frowned upon in the first place (as many algorithms can handle any data structure transparently), while they are kind of central in many functional languages. There are cases of course where frictionless shared ownership is nice and GC shines. Filter is not one of them.
- rbehrends 10y ago> Filters are used all the time in C++. The problem is that you need a vastly more complex machinery to cover all the various ways to avoid copying/reference counting and then still don't have a general solution when you can't avoid multiple ownership (unless you count std::shared_ptr with its very high overhead). As I said before, it's not that you can't do it, it's that there are costs associated with it. > If you need both the original list and the the filtered list, in a functional language you need to allocate new cons cells anyway. You can filter arrays also and allocating cons cells for a list is pretty cheap with a modern GC, as discussed before.