Arc Forumnew | comments | leaders | submitlogin
2 points by Pauan 4437 days ago | link | parent

Oh, and, here's "or":

  (def or
    [ %pattern-match (vau ~ {~ f e p v}
                       (any p -> x
                         (on-error (f e x v)
                           %pattern-match -> ~ %f))) ])
More complicated. Anyways, it chooses the first one that matches, and because of the way pattern matching is implemented (with first-class environments), it automatically does The Right Thing(tm) with duplicate names:

  (var (or {a} {a b}) {1 2})
---

I decided to look up how Racket does pattern matching, so I could see how its implementation of "or" differs from mine. I wasn't able to make heads or tails of how Racket defines "or", but I did find this:

  (and pat ...) — matches if all of the pats match. This pattern is often used
  as (and id pat) to bind id to the entire value that matches pat.
http://docs.racket-lang.org/reference/match.html?q=match#(fo...


2 points by rocketnia 4436 days ago | link

"I just made an interesting discovery: "and" is a superset of Haskell's "as" pattern"

How so?

The way I like to look at it, the correspondence between patterns and expressions is based on one being the inverse of the other. If a value is matched and then reconstructed using the same code, it should become the same value.

For example, since (cons a b) as an expression takes two inputs and gives one cons cell as output, its pattern version takes one cons cell as input and gives two outputs.

The pattern version of (and ...) would take one input and give at least one output. (I'm ignoring zero-argument (and) here.) If the input is nil, the outputs can be picked arbitrarily from the space of all possible values, as long as at least one of them is nil. If the input is not nil, then the last output will be equal to that value, and the other outputs can be any arbitrary non-nil values. Thanks to all this nondeterminism, an 'and pattern isn't exactly useful or even meaningful.

One refinement of this interface is to pick the outputs such that they're all equal to the input. That seems to be what you're going for. However, there are two caveats:

- The same implementation fits the requirements for a pattern version of 'or, so why choose one of these names over the other?

- If pattern-and's outputs are always equal, then expression-and's inputs should always be equal too, right?

We can dodge both of these conundrums by defining 'as as its own operator and just giving up on finding inverted counterparts of 'as, 'and, and 'or. It's okay for some computations to be non-invertible--unless, of course, the language design demands otherwise.

---

Besides inverting expressions, you and Racket (not to mention my past self) do some extra things with pattern matching beyond inversion: You allow the same variable to appear more than once in a pattern, and you allow backtracking when a match fails.

If the variables are seen as logic variables, then multiple variable occurrences make sense as multiple constraints to be unified. Racket and Nulan operations can only operate on values that have been determined up to equality, so the logic variable semantics degenerates into an equality test.

In another language, (and ...) as a pattern might constrain its arguments without actually making an arbitrary choice. (At this point it's even tempting to remove notions of "input" and "output" and just reason about a constraint graph, but I don't know how to work that way. In my vague thought experiments, the extra polarity comes in handy for choosing orders of evaluation and such.)

---

"Oh, and, here's "or""

I'm suspicious of the name "or," but if you understand what I'm saying about "and" above, I don't have anything more to say on that here.

With this alternation operator, you (and Racket and my past self) introduce backtracking at the level of subpatterns, rather than just at the top level of the pattern.

This is a bit curious for syntax purposes, since different branches impose different constraints on the logic variables. In particular, suppose one branch binds foo and another branch doesn't. If we end up in the branch that doesn't, then foo will be fully unconstrained.

Personally, I would expect this unconstrained foo to shadow nonlocal variables named "foo" just as if it were constrained to a particular value. However, if I tried to access it, I would expect some kind of uninitialized variable error. Is your version of "The Right Thing(tm)" the same as mine?

-----