Hacker News new | ask | show | jobs
by nicoburns 2183 days ago
> Is it just that pattern matching and enums were first popularized by certain functional languages?

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)

3 comments

> 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.

Ruby has pattern matching as an experimental feature: https://www.ruby-lang.org/en/news/2019/12/25/ruby-2-7-0-rele...
C++17 does - it's called std::variant.

  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
  }
That's not a language feature though, right? It's just a struct containing a union and a discriminant. That means:

- No pattern matching means your stuck with the awkward if-elseif-else

- It doesn't check that you've accounted for every possible variant.

- You can only hold one variant of each type: you can't have two variants that both contain a string.

- The specific instance isn't it's own type, so you can't implement methods on it.

A language that I am working on has those things "built-in" (it still generates compilable C++ in the backend). I did recently add pattern matching (and not just on variants) where the above would be equivalent to:

  $v:|[:int, :bool, :double] = 5;

  $vi: = v.[:int];
  $holds_int: = v.?[:int];

  switch v {
    .[:int]$i { /std cout << "got int:" << i };
    .[:bool] { /std cout << "got bool" };      
    .[:double]$d { /std cout << "got double: " << d };
  };
If a type repeats in the same variant, you would match by index to tell them apart:

  $v:|[:int, :int]<(.[0] = 5);

  switch v {
    .[0]$i0 { /std cout << "got first int:" << i0 };
    .[1]$i1 { /std cout << "got second int: " << i1 };
  };
(I have some ideas to allow named labels instead of numerical indices but that is not yet implemented)

It is, in spirit, an implementation of the inspect proposal [1], but with a (subjectively) much simpler and more powerful syntax (the grammar of the entire language is fully LALR(1) without ambiguities).

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p137...

Take a look at my reply above. Your right it isn't built into the language, but it does come with a helper method, std::visit, which does take into account that you've accounted for all possible variant types, and will throw a compile error if you didn't. You also aren't stuck with if, else-if style syntax.
You can use std::visit() for the time being, and pattern matching is being discussed.
This works out really nicely when combined with variadic template tricks. (Taken from CPP Reference)

  // Helper for creating anonymous visitor functions
  template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
  template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

  using var_t = std::variant<int, long, double, std::string>;

  std::vector<var_t> vec = {10, 15l, 1.5, "hello"};

  // Type matching visitor
  for (auto& v: vec) {
        std::visit(overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, v);
    }
Outputs: 10 15 1.500000 "hello"
That does not give you destructuring though, so to distinguish different variants of the same type you are left with a classic `if` inside the lambda.

Also this has "language support" in the sense that it is (again) implemented via template meta programming, i.e. the compile time is impacted quite heavily.

See "std::visit is everything wrong with modern C++": https://bitbashing.io/std-visit.html

This standards proposal would add proper match (named, of course, inspect) support: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p009...

Yes, I'm definitely not saying that std::visit is perfect by any stretch of the imagination
What a ridiculous language C++ is.