> ...
> Other Rust features, such as pattern matching and enums, which we’ve covered in other chapters, are influenced by the functional style as well.
What is it about pattern matching and enums that associates them with functional programming? Because going by the description of functional programming above, I don't see how they fit in. Is it just that pattern matching and enums were first popularized by certain functional languages?
Basically that.
People associate all kinds of different thinks with the term functional programming. The only think in common is that it's focused on functions, including first class functions.
For other thinks including things like (strict) immutability, lazy evaluation, syntax, algebraic data types, linear typing, etc. it is different from person to person weather or not they think about this as part of FP or just another think which happens to be part of the FP language they use/looked into.
You walk structures to extract information and derive a new structure. With immutable types and automated constructors this becomes regular enough to be baked in the language. Other languages like memory mutation so the main action is setting a variable to modify it.
Not only were Sum Types first popularised by functional languages, most imperative/OO languages still don't have them (although I'm not quite sure why not). E.g. none of Java/C#/C++ have them. Neither do Python/Ruby/JavaScript/PHP (although the need is somewhat reduced in dynamic languages)
I fully agree. Pattern matching is specifically useful for statically typed languages, where you can determine whether the patterns are exhaustive. Here pattern matching plays really nicely with the premise of having statically typed consistency guarantees.
In dynamically typed languages there is a use-case which makes sense: You want to conditionally extract nested values. But typically you have destructuring and a huge generic tool-set of predicates and transformation functions to do this. The advantage of being dynamically typed is how those things freely compose at the cost of having fewer run-time guarantees.
std::variant<int, bool, double> options;
options = true;
bool value = std::get<bool>(options);
bool has_bool = std::holds_alternative<bool>(options);
// or test which alternative is held
if (auto i = std::get_if<int>(&options)) {
// do something with int
} else if (auto b = std::get_if<bool>(&options)) {
// do something with bool
} else {
// do something with double
}This is pretty core to functional programming languages like haskell, and its very similar to how rust behaves, and is omnipresent much like in haskell. For example, rust does not have exceptions, but repersents errors via the `Result` enum which is analogous to Haskell's `Either` data type. In order to access any value wrapped in an Result you must pattern match on it and explictly handle the possibility of an error.
All of this comes from functional programming languages, but they are starting to become common place in mainstream languages.
C does have unions, but you can't distinguish which field is safe to read without additional information. Discriminated unions are a design pattern built on top of the language feature to give a true sum type, but since they aren't core to the language, pattern matching still isn't a thing.
(Technically, "pattern matching" encompasses both dispatching on sums and destructuring assignment on products, and to be fair, not a lot of imperative languages have good support for the latter either. But many do!)
What they lacked was usually a nice way to operate on tagged unions, ie. pattern matching.
(define (f x)
(match x
[`(,x ,x ,y) y]
[_ #f]))The procedural 'if x.len == 3 and x[0] == x[1]' seems a lot clearer as to what is actually being tested.
In that case you're passing around a function, which matches how this chapter characterizes "functional programming".
Or I could chain an ok_or_else on an Option but ugh now Rust is complaining that I'm capturing a reference to self. Screw it, I'll rewrite it to be an if let with a return. Part of the problem there is that we know an ok_or_else with try! will execute the closure and return if the value is None, but Rust's borrow check doesn't know that.
None of this is Rust's fault. It's just that it's hard to combine ergonomic closures and borrow checking.