Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rat Pats? #43

Open
pigworker opened this issue Apr 4, 2020 · 22 comments
Open

Rat Pats? #43

pigworker opened this issue Apr 4, 2020 · 22 comments
Labels
enhancement New feature or request question Further information is requested shonkier Everything related to the language

Comments

@pigworker
Copy link
Contributor

Would we like patterns for rational numbers? If so, what are they? We can currently test whether a value is a particular constant. Of course, we can do a bunch of stuff with right-programming. We could leftify that by adding guards. But is there any value to making the pattern language more expressive? (Haskell's n+k patterns are a data point in this space, but they were always perplexing.)

We could consider

  1. A pattern to test if a value is a number.
  2. Patterns which enforce an interval of acceptable matches (e.g., being at least 0).
  3. Patterns which enforce being not only rational but integral.
  4. Patterns which invert affine functions of one variable.

It's a bit late for an April Fool, but I think it would be hilarious to make

foo(x + y, x - y) -> [x y]

mean the same as

foo(a, b) = [(a + b)/2 (a - b)/2]

We should be guided by power-to-weight ratio, and by readability concerns.

I guess we should do infix first.

@pigworker pigworker added enhancement New feature or request question Further information is requested shonkier Everything related to the language labels Apr 4, 2020
@gallais
Copy link
Member

gallais commented Apr 4, 2020

Would it make sense to have numeric splices? E.g.

  • `n`/`d` matches numerator & denominator
  • `i`.`f` matches the integral & fractional part

You could of course write 0.`f` , `n`/1 to respectively match a
number between 0 and 1 or an integer.

If we add support for signs in rational literals, we can also throw them in
for rational splices & thus have patterns for pos & neg numbers.

@fredrikNordvallForsberg
Copy link
Member

Gut feelings:

  1. It's certainly useful to be able to make sure we get a number. One way to do this could be n@(a/b), if we allowed matching on numerators and denominators.
  2. Enforcing an interval feels like a guard job to me; I don't think the notation would be much heavier, and it would then also allow unions and intersections of intervals etc.
  3. @gallais suggestion of matching on n/1 to match on integers only makes sense to me. At first, I thought this was important, but actually I imagine that a lot of the time one can get away with pushing this requirement to an informal invariant and just happily fail if not given an integer.
  4. Inverting affine functions also seems like something it's not worth complicating the pattern language with for the sake of avoiding some right programming.
  5. I like n/d as a pattern; are the splice delimiters `n`/`d` necessary? It feels a bit heavy to have four ` for two patterns. Also while the fractional parts patterns are cute, I wonder if they are worth the complications?

@gallais
Copy link
Member

gallais commented Apr 4, 2020

Fractional parts make rounding easy. If you know you may only have
.25, .5 and .75 for instance, you can write:

round(x.25) -> x.5
round(x.75) -> x+1
round(x)    -> x

But then again maybe that's better solved by a primitive.

Good point about the antiquote. We just need to accept identifiers or literals.

@pigworker
Copy link
Contributor Author

I guess we need to decide whether n/d means matching the lowest terms literal representation, or whether we're "solving". Does pattern n/2 match 1, binding n=2, or does it fail?

@pigworker
Copy link
Contributor Author

Is there a bar in this bikeshed?

@fredrikNordvallForsberg
Copy link
Member

More gut feelings: I think I would be surprised if 2/2 wasn't matched by n/2. The other behavior should be achievable by adding a guard n < 2 && n > -2.

@pigworker
Copy link
Contributor Author

So when we write n/d, we're looking for whole number solutions? Making d other than 1 achieves nothing, because you can always multiply the numerator. Meanwhile 60/d is a pattern matching divisors of 60.

I'm surprised if n/1 fails to match any number. Using p = e notation, I expect n / 1 = (1 / 2) / 1 to succeed, binding n to 1 / 2. That is, if the pattern looks like the expression, the behaviour I expect is inversion.

@fredrikNordvallForsberg
Copy link
Member

What if we only look for integer n and d? Otherwise indeed failure is never really an option, eg wouldn't it make just as much sense to match 60/d = a/b for any a and b, binding d to 60b/a?

@pigworker
Copy link
Contributor Author

There are division by 0 issues, but there ought to be some perks for working in a field. I can't help but think that k op p and p op k should just do the relevant inversions, failing if pathological (e.g., 0 * x should always fail, because there is never exactly one solution x).

Using x/1 as the test for being an integer is peculiarly noncompositional. Testing for being an integer could well be a guard. Or a separate pattern like # p.

@pigworker
Copy link
Contributor Author

I guess we ought to do guards. But that opens another can of worms...

@gallais
Copy link
Member

gallais commented Apr 4, 2020

Or we could write x.0 to test for integers.

@pigworker
Copy link
Contributor Author

pigworker commented Apr 4, 2020

I think p.0 makes sense (matches integers which match p). I wonder if mantissa patterns make sense, though. I kind of want to be able to split something into integer floor and thing in [0,1) but I'm not sure pz.pm is such good notation for that. I mean

mod5((q.r)*5) -> r*5

is kind of cute, but

foo(x.5) -> x

does not match 0.5.

@gallais
Copy link
Member

gallais commented Apr 4, 2020

foo(x.5) -> x does not match 0.5.

Why not?

@pigworker
Copy link
Contributor Author

Because the fractional part of 0.5 is 0.5, not 5. OK, we could hack that so 5, 25 and 75 are treated magically.

@gallais
Copy link
Member

gallais commented Apr 4, 2020

We may be talking past each other. I would expect:

  • { x.y -> [x y] }(3.4) to evaluate to [3 4]
  • { x.4 -> [x] }(3.4) to evaluate to [3]
  • { 3.x -> [x] }(3.4) to evaluate to [4]

So I would absolutely expect { x.5 -> [x] }(0.5) to evalute to [0].

That's why I was talking about splices earlier: I would expect `e(a,b,c)`.`f(s,t)` to evaluate to 3.4 if e(a,b,c) evaluates to 3 and f(s,t) evaluates to 4.

@pigworker
Copy link
Contributor Author

What's { x.y -> [x y] }(3.04) ?

@pigworker
Copy link
Contributor Author

I expect { x.y -> [x y] }(3.4) to yield [3 2/5].

@pigworker
Copy link
Contributor Author

It's entirely possible that this splitting operation is a good idea, but using the decimal point as its notation is not.

@fredrikNordvallForsberg
Copy link
Member

It's entirely possible that this splitting operation is a good idea, but using the decimal point as its notation is not.

Indeed. A conservative option is to follow Haskell and not allow pattern matching on rational numbers, ie leave all that for the right/guards on the left, and just introduce notion for matching a number.

@pigworker
Copy link
Contributor Author

Yes, that's the bare minimum. It's funny, for strings that's

"`x`"

We should be careful not to create hostages to fortune. We can certainly have a boolean function which tests for being a number, and then the question is how to guard by that.

@gallais
Copy link
Member

gallais commented Apr 4, 2020

I still don't understand the argument against x.5 matching 0.5
and returning (x = 0). Surely when the RHS of the decimal point is
concrete, we are matching literally on the value and that means
unifying x with 0 and 5/10 with 0.5?

Similarly 0.x would match 0.5 and return (x = 1/2).

As for matching against a number, I guess x@(_._) or x@(_/_)
does the job if we do have these kind of patterns.

@pigworker
Copy link
Contributor Author

It's a hack, but we might just get away with it.

We need to notice when what is right of the dot is a literal mantissa. x.5 and x.05 are not the same pattern, even though 5 and 05 are the same number.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested shonkier Everything related to the language
Projects
None yet
Development

No branches or pull requests

3 participants