A few alternative designs were discussed in greater detail. Denoting
\cases
as (1), these are
(2): Comma-separated ``case``
Example:
filter = \case _, [] -> []
p, x:xs | p x -> x : filter p xs
| otherwise -> filter p xs
This alternative does not introduce a new construct. It instead
consists of a straightforward extension to an existing one: Allow
separating multiple patterns in \case
by commas. This makes it
the least disruptive of the presented alternatives.
A clause would only match if all of its patterns match their
respective scrutinee.
Rather than introducing a new extension, this behavior would be
enabled by -XLambdaCase
.
Additionally, an analogous extension could be introduced for
case of
:
case numerator, denominator of
_ , 0 -> Nothing
Whole n , d -> Whole (n `div` d)
Complex a b, d -> Complex (a `div` d) (b `div` d)
This can be used instead of using tuples to achieve something
similar:
case (numerator, denominator) of
(_ , 0) -> Nothing
(Whole n , d) -> Whole (n `div` d)
(Complex a b, d) -> Complex (a `div` d) (b `div` d)
With the advantage that users don’t have to be worried or learn
about whether using tuples in such cases incurs a performance
penalty, and it would mean that the \case
syntax stays
consistent with case of
syntax.
This extension to case of
would be enabled regardless of
whether or not -XLambdaCase
is turned on.
If no clauses are given, i.e. the expression in question if
\case {}
, how many arguments this expression should take is
ambiguous. However, currently, with -XEmptyCase
, this
expression is already valid and takes a single argument. Thus, to
maintain backwards compatibility and for lack of a better option,
this proposal does not alter the behavior of this expression.
In general, the lack of parentheses makes this alternative
slightly more concise than the others, especially in cases with
only a single pattern.
One potential concern is that this breaks the pattern of symmetry
between expressions and patterns that match them. For example, if
a function is defined as f (Just a) (Right b) = a + b
, it can
be called as f (Just a) (Right b)
, but when using \case
(i.e. f = \case Just a, Right b -> a + b
), the patterns are
separated by commas, whereas the expression calling f
still
uses parentheses.
(3): One lambda per clause, ``case of``
Example:
filter = case of \_ [] -> []
\p (x:xs) | p x -> x : filter p xs
| otherwise -> filter p xs
The functionality of -XLambdaCase
is extended, according to
the following schema:
case [ scrutinee ] of
[ Pattern_0a ] \ Pattern_1a ... Pattern_na -> Expression_a
[ Pattern_0b ] \ Pattern_1b ... Pattern_nb -> Expression_b
...
Semantically, this would be equivalent to
\var_1 ... var_n -> case ([ scrutinee, ] var_1, ..., var_n) of
([ Pattern_0a, ] Pattern_1a, ..., Pattern_na) -> Expression_a
([ Pattern_0b, ] Pattern_1b, ..., Pattern_nb) -> Expression_b
A new extension -XExtendedCase
is introduced. With this new
extension enabled, the case
expression is able to define
anonymous functions. The scrutinee may be omitted, in which case
the corresponding pattern in each clause must also be omitted.
Furthermore, in each clause, between the usual pattern (if it is
present) and the arrow, a \
and a number of patterns may be
written. The number of patterns must be consistent across all
clauses, and the types of corresponding patterns must match (e.g.,
the first pattern after the backslash must have the same type for
all clauses). As usual, case
clauses can contain guards as
well.
The number of patterns after each \
determines the arity of
the function that a case
expression produces. The nth
pattern after the \
is matched against the nth argument
given to the function.
Note that the patterns after the \
must be enclosed by
parentheses if they consist of more than one token, just like
patterns in a lambda expression, but unlike the pattern that can
come before the \
.
If there is no scrutinee, it is not immediately clear what the
meaning of an expression without clauses, i.e. the expression
case of {}
, should be, since the number of arguments to the
anonymous function is not specified. Users might expect this to
compile if the -XEmptyCase
extension is enabled. However, due
to the inherent ambiguity, this proposal does not allow a case
expression that lacks both a scrutinee and clauses. Other
approaches are possible, see Alternatives section.
Like the existing behavior for alternatives in case
expressions, and equations in function declaration syntax, it is
possible to use where
clauses within each clause of the
extended case
expression. Furthermore, each clause can have
guards, which appear after all patterns.
This alternative has some desirable properties, in that it extends
an existing syntactic construct rather than introducing a new one
and is syntactically similar to lambda expressions. On the other
hand, it does not relate to existing syntax as directly as the
alternatives (e.g., it produces an anonymous function but doesn’t
start with \
, as opposed to lambda expressions and \case
),
and its functionality overlaps with that of \case
.
Since the introductory example only demonstrates the case without
scrutinee, here is a different example:
sendEmail :: Text -> Text -> Text -> Maybe Attachment -> IO ()
sendEmail address = case validate address of
Just emailAddress \subject content (Just attachment) -> sendWithAttachment emailAddress subject content attachment
Just emailAddress \subject content Nothing -> sendWithoutAttachment emailAddress subject content
Nothing \_ _ _ -> error "invalid address"
(4): Multi-pattern ``case`` with parentheses
Example:
filter = \case _ [] -> []
p (x:xs) | p x -> x : filter p xs
| otherwise -> filter p xs
Regular function definition syntax requires parentheses around
patterns that consist of more than one token. The same could be
done with \case
. This would make the two syntaxes more
consistent, and allow easy refactoring from one to the other. It
also doesn’t introduce any new syntactic constructs that have to
be maintained.
However, it behaves differently from the current -XLambdaCase
extension, which doesn’t require parentheses around patterns. This
would seem to make it non-backwards-compatible, especially if it
still uses the same -XLambdaCase
extension name. This can be
mitigated in various ways.
First, a refactoring tool could be provided to update existing
code and introduce parentheses where necessary, which would
massively lower the effort required to update old code to be
compatible with the new extension. Note that while the current
-XLambdaCase
extension doesn’t require parentheses, it
doesn’t prohibit them, either. Thus, code updated with such a tool
would work with both versions of the extension.
Second, a special case could be introduced to have the compiler
handle \case
differently if there is only one pattern. More
specifically, this means that the type checker detects when the
first pattern in a clause is a solitary, non-nullary constructor.
If this is the case, the AST is reconstructed such that the
remaining patterns in the clause become arguments of this
constructor.
When this special case is triggered, the compiler would produce a
warning (-Wdeprecated-lambda-case
), which would be on by
default, and warn the user that they’re using syntax which will be
deprecated at some future point. This would make it possible to
remove the special casing and warning after a few releases have
passed.
Summary:
Approach
| Example
| Pros
| Cons
|
---|
Keyword (\cases , \cases , etc.)
|
\cases (Just a) (Left b) -> ...
_ _ -> ...
| | |
Comma-separated \case
|
\case Just a, Left b -> ...
_ , _ -> ...
case Just 34, Right [] of
Just a, Left b -> ...
_ , _ -> ...
| Conceptually, the smallest change that achieves the goal:
Just a minor extension to one or two existing constructs
That means no demand for or concerns about potential
deprecation
Parity with extended case … of syntax
Like current \case , single pattern uses are
concise due to lack of parentheses
| |
One lambda per clause case of
|
case of
\(Just a) (Left b) -> ...
\_ _ -> ...
case [1,2,3] of
(x:xs) \(Just a) (Left b) -> ...
_ \_ _ -> ...
| | While it extends an existing construct, this extension
makes it overlap with \case functionality
Not as obvious an extension from existing syntax as
the other options (i.e. starts with case, not </tt>,
even though it takes arguments)
|
Multi-pattern \case with parentheses
|
\case
(Just a) (Left b) -> ...
_ _ -> ...
| Doesn’t introduce a new construct, and doesn’t
introduce any overlap with others
Parity with function equation syntax
| |