The Ultimate Conditional Syntax

(dl.acm.org)

59 points | by azhenley 13 hours ago ago

10 comments

  • dan-robertson 3 hours ago

    I like that there is this left-to-right flow. I think it’s a bit nicer to read than if-let ordering where the pattern comes before the thing it will be matched against. I think it’s also good for ocaml-style constructor disambiguation, which tends to go in lexical order.

    Another nice aspect of making guards a less special case is that it avoids complexities in deciding if a binding is unused. I believe this logic was a source of lots of compiler warning bugs in ocaml.

    This syntax doesn’t seem to solve the following problem with matching where there are two paths to the same binding, (say you have an option in one branch but it isn’t optional in the other, and maybe you’d like to handle both cases with the same code. Currently you can do that with a match (match …) with … pattern.

    I worry that the semantics around exhaustiveness and mutable values may be confusing, though I guess OCaml already has that problem:

      type t = { mutable x : bool }
    
      let n = function true -> 1 | false -> 0
    
      let f t =
        match t with
        | { x = false } when ( t.x <- true; false) -> -1
        | { x } -> n x * 2 + n t.x
    
    What does t { x = false } return? Similarly if you changed the second case to be two cases instead of binding x?
  • clark800 4 hours ago

    Looks very similar to lambda zero syntax (https://github.com/clark800/lambda-zero):

        def getNaturalName(tag, globals)
            if globals.lookup("0") is Just(Global(_, _, term))
                if term is Numeral(_, type, _)
                    return Just(Name(getTermTag(type)))
                error showSyntaxError("0 must be a numeral to use numerals", tag)
            return Void
    
    Though this ultimate conditional syntax is more general because lambda zero only allows one destructuring per conditional to simplify parsing.
  • huqedato 5 hours ago

    Elixir already has this - "with". https://www.openmymind.net/Elixirs-With-Statement/ E.g.:

      with true <- is_email_address?(email),
         true <- String.length(code) === 6,
         %EmailConfirmation{} <- EmailConfirmations.get_email_confirmation(email),
         nil <- EmailAddresses.get_email_address(email),
         {:ok, user} <- Users.create_user(data),
         {:ok, email_address} <- EmailAddresses.create_email_address(user, email) do
         ...
      else
         ...
      end
  • mgaunard 3 hours ago

    I don't understand how it's better than traditional pattern matching.

    • dan-robertson 3 hours ago

      With traditional matching there are up to five different things:

      - if x then y else z. This is roughly like a match with a true and false case but it depends on the language how much that is

      - match e with { p -> e }. This is the classic pattern match case

      - if let p = e then x. This is roughly equivalent to (match e with p -> x | _ -> ())

      - match e with { p when e -> e }. This is matching with a guard but I’ve counted it as a special case because it doesn’t easily designate into a match because of the binding/evaluation order, and the guard is special because it can only go at the top level of the match clause so it isn’t just a special kind of pattern (though maybe it could be)

      - let p = e. This is one-case pattern matching used for binding variables or destructuring.

      The paper proposes a way to make the first four cases obvious parts of one more unified thing, which makes the language potentially simpler and may reduce some weird warts like where guards can go.

      • EPWN3D 3 hours ago

        There's such a thing as too high an abstraction. Sometimes 4 different operations really are just 4 different operations and not cases of some mega-construct.

        • dan-robertson 2 hours ago

          I totally agree that can be the case, but I’m not sure it applies that much to the OP.

      • 082349872349872 3 hours ago

        Inspired by duality, I've been trying to work out a language where there's a more obvious correspondence/symmetry between expressions (which evaluate in an environment of named bindings to produce an anonymous value) and patterns (which destructure an anonymous value to produce an environment of named bindings).

    • trealira 2 hours ago

      It looks like it could be more concise sometimes. For instance, you could have this:

        if f(x) is Some(a) and g(x, a) is Some(Right(b)) then h1(a, b) else h2(x)
      
      The equivalent pattern matching with a match expression in pseudo-ML:

        match f(x) with
          None -> h2(x)
          Some(a) ->
            match g(x, a) with
              Some(Right(b)) -> h1(a, b)
              _ -> h2(x)