7 ms·
Because Rust-style resource management is not only about performance, but also about correctness. And the ML crowd cares a lot about the latter - rightly so. I
by catnaroek 9y ago
Because Rust-style resource management is not only about performance, but also about correctness. And the ML crowd cares a lot about the latter - rightly so.
I'd kill for a language with ordinary ML types for values (integers, strings, etc.), and substructural types for objects (file handles, database connections, etc.).
- nickpsecurity 9y ago"I'd kill for a language with ordinary ML types for values (integers, strings, etc.), and substructural types for objects (file handles, database connections, etc.)." Could you explain what that means and how it benefits compared to common languages discussed here? Those of us from imperative backgrounds mainly read these threads to learn ideas that we might move into such languages.
- zenhack 9y agoSo, from an executution model standpoint Rust is pretty close to C++ -- manual memory management, with RAII as the usual strategy for managing things. The big novelty is that the type system actually enforces this, so you can't have dangling pointers, or forget to close a file, or... But most languages have had a solution to the resource problem at least as far as memory is concerned for a long time: garbage collection. It has huge advantages over Rust's approach, in that you can basically not think about RAII or ownership at all; as long as you're not actively holding on to an object, the memory will get reclaimed for you. But this doesn't solve the problem for other resources like files, since when a file is closed actually matters semantically. You don't want to just let the garbage collector decide when to shut down a tcp connection. So what the gp is suggesting is, it would be nice to have a language that uses GC where it makes sense, as it's generally easier to work with, but also provides sane mechanisms for releasing resources like files and sockets.
- pjmlp 9y ago> But this doesn't solve the problem for other resources like files, since when a file is closed actually matters semantically. You don't want to just let the garbage collector decide when to shut down a tcp connection. At least in some languages this is not an issue if one is able to use higher order functions, with bonus points if they allow for trailing lambdas. So if it is possible to organize the application architecture as regions where the resources are supposed to be valid, then one can manually get rid of such resources. Of course, this might not always be possible, and there is the caveat that one needs to remember to apply such patterns. Which is easier if it can be imposed via the type system.
- catnaroek 9y ago> At least in some languages this is not an issue if one is able to use higher order functions, with bonus points if they allow for trailing lambdas. I am afraid you are talking nonsense. Higher-order functions make verification harder, not easier, because they hide the point at which control flow is transferred from one module to another. This is backwards, because this point should be prominent, in big neon letters, precisely so that you can tell exactly when a resource stops being available, or when an invariant goes from being “your responsibility” to “someone else's problem”.
- zenhack 9y agoI think pjmpl is referencing apis like this: with_file "hello.txt" (fun fd -> (* do stuff *) ) where you've basically created a c++ destructor style api with a lambda. It's certainly not "verification" in any formal sense, and the type system can't help you there any more than it can in c++. But this is a perfectly reasonable pattern.
- catnaroek 9y ago> where you've basically created a c++ destructor style api with a lambda. Except for the part where destructors are meant to be called no more than once per object, at the end of its lifetime. All that you can guarantee is that `with_file` doesn't call the destructor more than once. But that's not terribly interesting.
- zenhack 9y agoYou can screw up destructors in exactly the same ways. And it also serves to make sure you close the file at all. But again hence the value of having the lifetime of the object enforced by the type system.
- catnaroek 9y agoIn the presence of reference-counted mutable objects, destructors no longer guarantee that cleanup will happen. But that much is okay. This is a liveness property, and, as far as I can tell, nobody really knows of any non-annoying way to enforce liveness properties with types. On the other hand, “cleanup is the last operation that can be performed on an object” is a safety property, and types are excellent tools for verifying properties of this kind.
- deleted 9y ago[deleted]
- zenhack 9y agoWorth pointing out, the resource management strategy isn't even necessarily a net positive for performance. In typical GC'd languages you have a much richer design space for your memory system. You can move stuff around, you have flexibility as to when you do collection, you often have allocation as a built-in primitive, which means the compiler knows about it and can help optimize. The end result is that good GC-based memory systems tend to perform better than malloc/free style APIs (with the call to free() possibly being implicit). to get good performance in C/C++/Rust, the programmer needs to be concientious about allocation. You have more control, but also more responsibility. In OCaml, allocation is bumping a pointer -- go nuts.
- dpc_pw 9y ago> The end result is that good GC-based memory systems tend to perform better than malloc/free style APIs Citation needed. GCs promise wonders, and yet in practice they are going to eat your memory, trash caches, and slow everything down. Performance benefits are typical "in some applications, in some use cases, etc." not "tend to"
- zenhack 9y agoOkay, yeah, I should temper that statement. This stuff is super hard to actually study rigorously, and I certainly don't have a reference to support as broad and vague a claim as "tend to perform better." And of course "perform" is a very muilti-dimentional thing, not a single value. The truth of the matter is that any generic memory management strategy is going to fall on it's face in certain scenarios; most folks have seen first hand the raii failure mode where some application takes forever just to exit, because it's pointlessly calling god-knows how many destructors. It's late, and I should generally try to be more clear with myself about what my point is before I post. The thing in there that I think is more salient is, with manual memory management you're opting into control, not magic everything-is-faster-now. I don't think the op was suggesting it, but people often act like using a lower level language is going to always make things faster, ans it just isn't true. Idiomatic, decently performing OCaml is going to make for some very slow Rust.
- pjmlp 9y ago
- mratsim 9y agoI think Nim or Ada would fit your needs: - very strongly typed, you can even make sure you don't add Miles and Meters together, and that Meters * Meters give you square-meters - destructors
- thesz 9y agoThey are not all that well typed. You can't have constrained types, for example (available with type classes in Haskell). I spent long time with VHDL, which is Ada with third leg. It is not nice language to work with.
- mratsim 9y agoWhen you say constrained types do you mean generics? You have generics and concepts (similar to C++17) in Nim, not too sure about Ada.
- thesz 9y agoI meant types with constraints, like this one: sum :: Num a => [a] -> a The type variable a is constrained to have implementation of interface Num (integer constant (0), addition, subtraction, etc). These constraints can be much deeper. For example, I can define a function that allow me to add two somethings if their "regions" (a property I know how to compute) is equal and the result will be produced in "region" of first argument: add :: (Region a ~ Region b, Region a ~ Region c) => a -> b -> c One use of something similar is addition of integer values represented by bit vectors in Verilog - you should not add bit vectors from different clock domains, otherwise you get unpredictable results.
- pjmlp 9y agoI also spend some time with stored procedure programming, mostly PL/SQL, most of them are also Ada based, and I find them quite productive, actually.
- catnaroek 9y agoAlgebraic data types and ML-style modules are non-negotiable.
- anilmujagic 9y agoTake a look at F# units of measure: https://fsharpforfunandprofit.com/posts/units-of-measure/ https://fsharpforfunandprofit.com/posts/units-of-measure/