9 ms·
This is a slight Rust tangent but I've been thinking a lot about the properties that make functional programming interesting and are called out in the article.
by vvanders 10y ago
This is a slight Rust tangent but I've been thinking a lot about the properties that make functional programming interesting and are called out in the article. Specifically the immutability providing an easy way to reason about functions via only inputs and outputs.
I feel the the memory model of Rust(single mutable ref or unlimited non-mutable refs) combined with the fact that there are no mutable globals(inside safe code) gives you a much easier system to reason about. I know based on inputs and outputs what could be mutated and since I know there's only one mutable reference I can trace that single ref easily.
It really feels like getting a large majority of the benefits of functional programming without sacrificing the performance and predictability we've all become accustomed to in non-functional languages.
- iopq 10y agoYes, I've noticed that as well. That's why there was even a suggestion to remove `mut` - because the compiler can figure out which things should be `mut` and which shouldn't. Functional programmers had a meltdown, but I wonder if that wasn't actually a better choice.
- lytedev 10y agoI don't understand why, in this situation, just make it a compiler option? What are the downsides to options like this? Confusion among Rust users when perusing another code base?
- hota_mazi 10y agoThe downsides of compiler options that modify the language is that code can no longer be understood on its own. Most languages stay away from these for that reason. One notable exception is Haskell, which uses pragmas to modify the language. At least these pragmas are part of the source code, but they absolutely need to be included if you want people to understand a snippet of your code. Take a look at Haskell questions on StackOverflow to see what I mean. It's not something you want to see in an industrial strength language because it basically turns your language in factorial(n) languages, where n is the number of pragmas.
- dualogy 10y ago> It's not something you want to see in an industrial strength language because it basically turns your language in factorial(n) languages, where n is the number of pragmas. I don't see this as an issue because here it's just simply an intrinsical part of the "language". Nobody writes the core language consisting of just some ~6 primitives, everything on top is syntactic sugar and language-extension pragmas are just the same principle for the more obscure/experimental (but in no way unstable/less robust) ones.
- arcbyte 10y ago> Nobody writes the core language consisting of just some ~6 primitives https://en.wikipedia.org/wiki/Lisp_(programming_language) https://en.wikipedia.org/wiki/Lisp_(programming_language)
- kazinator 10y agoA discussion on minimal forms is here: http://www.faqs.org/faqs/lisp-faq/part1/section-6.html http://www.faqs.org/faqs/lisp-faq/part1/section-6.html "Nobody" implements practical Lips with just six special forms. To get a ridiculously low count, you have to leave a lot to uncounted library function. Such as, oh, any numeric support. Programmers want everyday things like (+ 2 3.0) to be implemented as primitives.
- Buttons840 10y agoIf a language will have N number of features I'd rather have some behind pragmas than not. Let's say by some measure GHC Haskell and C++ have the same number of features. C++ will always be N factorial, unless I read the entire source code to see that certain features are not being used. In Haskell I can look at the top line and easily see that many features are not being used. An interesting alternative (one which a C++ compoler could employ) is to allow pragmatic that disable features. E.g. I could quickly see that a C++ file is not using macros. This is the wrong default though. Features should be opt-in rather than opt-out to encourage a minimal use of features.
- 10y ago
- iopq 10y agoThere's probably less opportunity to make an error when most things are immutable by default. I'm not sure if that's the case or not.
- caconym_ 10y agoObviously it fragments the language in a way that's harmful for human readers as well as software like compilers, editors/IDE plugins, and so on. How to know which variant of Rust you're reading? As you add more options that change the syntax and semantics of the core language, these difficulties will be compounded. Maybe a bit of fragmentation isn't the end of the world, but I think you'd need a really good argument in its favor and in this case frankly I don't see one. Additive extensions/features (like GADTs in Haskell) are an easier sell for me because a) they actually add something useful and b) they generally don't change the core language, but rather sit on top of it.
- dkersten 10y agoI see it as part of the documentation - sure the compiler doesn't need it, but it's useful to me. (Although I say that as someone who is interested in rust and learning rust, but hasn't yet wrote any substantial code in it)
- nine_k 10y agoDocumenting and enforcing important intentions is super useful, and is all over the place in statically checked languages.
- wyager 10y agoImmutability is one of many advantages of functional programming. Other advantages include higher-order functions (which Rust cannot support very well with its memory model), stronger type systems (only possibly because the Lambda calculus is on much better mathematical footing than any production imperative language semantics), and better/safer state management via ADTs (which Rust has decent support for). That's the risk of reading articles like the OP, where they show you a couple functional tricks in an imperative language that inherently can't give you the whole picture. You get too limited an idea of what it means to use modern functional techniques. You simply cannot do anything approaching cutting-edge functional programming in a language like Java or JavaScript or Rust. They all lack some important aspects of what makes functional programming useful.
- tps5 10y agoCan you explain why Rust "cannot support higher order functions very well?" I'd also like to hear about "what makes functional programming useful" if you have a minute.
- wyager 10y agoSure. It's just because when you're passing around functions of type (a -> b) the only fixed-size representation is every possible input/output pair, which is obviously not a good representation. Instead we use a variable-size representation in the form of code (passed around as a code pointer) and captured variables (the format of which depends on the function being passed around). Together these are called a closure. Using variable-sized closures created at runtime requires dynamic allocation, which rust allows for but does not make convenient (intentionally). Sometimes you can get around this but not always. So the kind of stuff you can do with higher-order functions in rust is more restricted and usually has extra rigamorole required for memory management. It's not that rust is doing something wrong, it's just a necessary consequence of having such excellent guarantees about allocation (or lack thereof). > I'd also like to hear about "what makes functional programming useful" if you have a minute. This is sort of a book-length topic and I'm on my phone, but a few points worth looking up are (Generalized) Algebraic Data Types, Typeclasses (in particular Functor, Foldable, Applicative, Traversable, Monad (in particular Maybe, Either, and State)), Higher Kinded Types, corecursion, the Y combinator. The gist of it is that you can do a bunch of cool stuff you wouldn't be able to do (or even think about doing) in an imperative language. For some reason we're not entirely sure of, it seems to be way easier to isolate the essence of what we're trying to accomplish when using functional programming than when using imperative programming, and therefore to automate the boring work normally associated with doing things to data, like writing loop definitions. It's kind of like how natural functional type systems have the same derivation semantics as various logical systems, even though it's not entirely clear why that ought to be the case. We seem to have just stumbled upon an abstraction that meshes nicely with the platonic universe of useful computer programs, as opposed to an abstraction that only exists because of the particulars of how our computers work.
- Wildgoose 10y agoShared mutable state is the problem. Functional programming addresses this by eliminating mutability, but at the cost of losing many valuable algorithmic approaches. (The "quicksort" example usually used to illustrate Haskell doesn't perform a true quicksort and is horrendously slow). Rust deals with it by eliminating sharing via its affine type system. I have only just started exploring Rust but I'm very impressed, it seems the perfect blend of theory and practicality so far.
- mbrock 10y agoTo be clear, Haskell absolutely supports shared mutable state, even in sophisticated ways like transactional memory. It's also mostly straightforward to implement a fast Quicksort using mutable arrays in Haskell. The difference is that all that stuff is formulated within a theory based on pure evaluation of functional expressions. Loosely speaking it's like how the math that physics uses doesn't have mutable variables, but it still manages to express time varying phenomena...
- Athas 10y ago> It's also mostly straightforward to implement a fast Quicksort using mutable arrays in Haskell. Do you know of one? All the fast array-based quicksorts I have seen in Haskell have been very awkward compared to implementations in imperative languages with good array support (and they have been quite slow as well). I don't feel like this is much of an indictment against Haskell. Sure, a functional language isn't a good fit for a very imperative algorithm, but that's not really its target domain.
- elihu 10y agoAs a Haskell programmer who's done a little bit of Rust, I agree. In Haskell, we do have an escape hatch in the form of the ST monad, which allows you to write an imperative algorithm with mutable state and call it directly from pure code. This is safe (i.e. it doesn't break referential transparency) because there isn't any way to introduce nondeterminism. ST is more restrictive than the IO monad. You can't do file I/O or use concurrency. (You can use the same parallel evaluation tools that are available in pure code.) So, pure Haskell code plus ST is definitely still more restrictive than Rust safe code, but the difference isn't quite as large as it might appear at first. And sometimes Haskell's added restrictions are a good thing, if that's what you want. It is pretty interesting that if you start with the premise that sharing + mutability is bad, you can go either in the direction of Haskell and disable mutability (and then re-enable it in some contexts that are known to be safe) or the direction of Rust and allow sharing and mutability, just not at the same time. Both seem to be good strategies that will appeal to somewhat different audiences.
- LeanderK 10y agoi think it is important to understand the practical restrictions the languages are created for. You don't use haskell for space/latency restricted systems, but (as a much as i admire rust) haskell is easier to program with if you have (most of the) topics where that don't have these hard restrictions. Haskell is concise and short but still performant. Haskell, after all those years, has potential. Haskell is simple, but not always easy, to reason about, especially as a beginner ;). Every tool has their up- and downsides. While the ST-monad has their restrictions, these restrictions contain much power in the hands of compiler engineers because of the guarantees you have. I am a strong Proponent of a "no free-lunch theorem" (control vs optimisability) for programming languages and don't like the abundance of multi-multi paradigm language because they end up being no good at anything. Just look at Java-8s horrible lambdas and pseudo FP, it's not low- but also not a great high-level.