Internal Guards

suggestion
#1

Continuing the discussion from… Expat - composable, reusable pattern matching

That’s a compelling idea. Thinking out loud about this from a semantics approach.

In arguments

If we allow that then I think it makes sense to allow guards to be arbitrarily deep in the arguments list, for instance inside data structures:

def pos_distance({x when is_integer(x), y when is_integer(y)}) when x + y > 0 do
  :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2))
end

This is the most blatant point of contention I can think of. The alternative is to require that internal guards be trailing a parameter rather than any further in the AST.

Logically you’d expect internal guards to be and-ed with trailing ones, ie:

when (internalGuard1 && internalGuard2) && trailingGuards

Scoping

Trailing guards benefit from not having to be aware of scoping, so that this typo is impossible:

def pos_distance({x when is_integer(y), y when is_integer(x)}) when x + y > 0 do
  :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2))
end

I’m not sure how that should be handled.

Mutli-guard functions

How would we handle multiple trailing guards? My understanding is that they are not often used, but logically equivalent to or-ing each one together (with different exception behaviours that make certain things possible). So a naive macro might try:

when internalGuards && (multiguard1 || multiguard2)

However that doesn’t preserve the exception behaviour, so the correct macro rewrite would have to be

when internalGuards && multiGuard1
when internalGuards && multiGuard2

Internal multi-guards

This also invites speculation about the opposite construct, how would we resolve the non-sensical inner multi-guard after the point in the example:

def pos_distance({x, y} when x > 0 when y > 0, pow when is_integer(pow)) when x + y > 0 do
  :math.sqrt(:math.pow(x, pow) + :math.pow(y, pow))
end

I can’t think of an intuitive way to handle this without making the rewrite explode exponentially for every permutation of each set of inner multiguards and trailing multi-guards. I think nested whens in the AST would have to raise syntax errors early within arguments lists.

In cases

I think the construct makes sense for cases as well as functions, though it’s even uglier when written by hand in daily use:

case point do
  {x when is_integer(x), y when is_integer(y)}) when x + y > 0 ->
    :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2))
  _ -> 0
end

It might come in handy in some really deep matching that you’ve chosen to spread across multiple lines rather than refactor, though.

1 Like
Expat - composable, reusable pattern matching
#2

Hi @christhekeele! Great analysis of my half-baked proposal. :slight_smile:

Unfortunately this may hurt composition as seen in expat, so we do need to allow the guard anywhere in a match, even nested.

We can either do it as you mention or perform give visibility to all variables in a guard. I can only be sure if we decide to have a proof of concept or similar. What we definitely shouldn’t do is have it left to right (see variables as they appear in the match) as matches don’t behave as such.

Perfect.

I would just not allow them. :slight_smile:

Right. I think it can be a discouraged construct unless it comes from a macro, which is the primary reason to enable such.

#3

OCaml/Haskell/F#/Rust do not auto-combine at the syntax level, however all of the prior can do it automatically in some setups when they generate the output native machine code.

However, OCaml PPX’s and F# (EDIT: And Scala!) enhanced matchers can do it when you make an appropriate wrapper matcher.

I quite like your improvement ideas in expat though! Pulling out when's from arguments would be a definite help for some of my things too! :slight_smile:

That at least would be pretty trivial to do with macro so it doesn’t seem like it would be hard.

+1

They all seem like assertions me.

Ooo, good idea, this is entirely possible to do! however, then you couldn’t do something like compare different variables in differen when expressions without hoisting the when up, which I think is good from a safety perspective anyway.

Yeah, you’d need to put the internal guards on to each individual when expression on the outside, which is entirely fine.

Exponential permutations is really the only way to properly handle this without banning it outright. Though it’s not like the permutation count would get really huge in the great majority of cases (if ever).

I’d personally lean to allowing them myself, but I’m not strong on either side.

I think it would be nice to allow it without macros as it is a good scoping safety measure too.

#4

I think I’ll put together a proposal for the core mailing list to get more perspectives and maybe take a stab at it if I have energy over the next few weekends and nobody beats me there. :smiley:


I think scope tracking is something we could ignore initially and consider issuing helpful warnings for later. I don’t see the walk through the AST for it being too laborious. Any really silly typos should get caught post-transformation by the compiler as it’d be a normal guard.


Yeah, I’d probably allow it but only document it in passing at the bottom of the guard docs, and no-where else, much like multi-guards today. It’d be interesting if it became common usage but we should avoid advertising it to people just learning about guards and reading already written code first.


@vic I’ve flagged the ‘internal guard’ discussion I started as off-topic so it gets broken out and doesn’t divert exposure from Expat. It’s got some really nifty ideas and I can’t believe I’d never heard of it til now, great work!

2 Likes
#5

Beautiful! Let’s get @vic’s feedback too and make sure that it will be actually useful for expat to. :slight_smile:

2 Likes
#6

Discussion moved to the mailing list. :+1:

1 Like