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

(Proposal) Match Expression #191

Closed
AdamSpeight2008 opened this issue Jan 31, 2015 · 7 comments
Closed

(Proposal) Match Expression #191

AdamSpeight2008 opened this issue Jan 31, 2015 · 7 comments

Comments

@AdamSpeight2008
Copy link
Contributor

Match Expression

This proposal is separated out from #180. The approach to the syntax is an alternative to the one proposed by @MadsTorgersen

A match expression block is an expression as it permits uses in situations that switch can not be used. For example in-conjunction with a return

return match( ... ) { | _ => ... }

or an 'await' this needs further investigation

var result = await match( ) { };
  • |
    donates the start of a match clause
  • into
    allow you to introduce new variables into the scope (of this match clause), or reuse existing in scope variables.
    ** := is for introducing a new variable into scope.
  • when
    is for specifying that this match clause is considered if the predicate is satisfied.
  • => this doesn't indicate the the expression after is a lambda function, it just reusing an existing symbol as it's is close to what you use for lambda. Eg multiple statements / expression are required to be enclosed inside braces { ... }. Not needing a return for simple single expressions.
    The code called when the match clause is satisfied could be an expression, a statement or an exception.You can't intermingle expression and statements.
    Eg
match ( x ) with
{
  | 0 => Console.WriteLine("Zero");
  | 1 => return "One"; // Error not a statement;
  | _ => Throw New Exception("FUBAR");
}


match ( x ) with
{
  | 1 => return "One";
  | 0 => Console.WriteLine("Zero"); // Error not an expression
  | _ => Throw New Exception("FUBAR");
}

The rationale for into before when is that is allows the variable just introduced to be used with the predicate of the of the guard expression (when).

... is being use in the following examples as a place-holder for some code.

Default Clause

Every match is required to have default clause, which is used if non of the other clause are satisfied. Examples will use | _ => to represent this. If all of args in the "target" are wildcards this is also consider a default clause. A default clause most also have no guards, so it can catch all possibilities..

Examples

match ( e , a ) with
{  
  | ( null, null ) => ... ;
  | ( _ , _ ) into 
       {l:= a.Left as IdentifierName;
        r:= a.Right as IdentifierName;
       } => match (l,r) with
            {
              | (null,null) =>  ... ;
              | ( _ , _ ) when (a.Name.name == r.Name.name) => ... ;
              | _ => ... ;
            };
  | _ => ... ;
}
try
{
  var res = match( l, op , r ) with
            { 
              | ( _ ,'+', _ ) => ... ;
              | ( _ ,'-', _ ) => ... ; 
              | ( _ ,'*', _ ) => ... ;
              | ( _ ,'/'. _ ) => ... ;
              | ( _ , _ , _ ) => throw new NotSupported(); // Also the default clause.
            }
catch (ex) when NotSuppored
{
  ...
}
match type ( o ) with
{
  | ( String ) into s when (s != null) => ;
  | ( Short  ) into s when (s >= 0 ) => ; // not an error 
  // ...
}

Grammar

match_expr     ::= "match" match_category? '(' match_source ')' "with"
match_source   ::= source (',' source)*
match_body     ::= '{' clause* default_clause '}'
match_category ::= match_type
match_type     ::= "type"
clause         ::= '|' target into? when? result
default_clause ::= '|' '_' "=>" expr ';'
target         ::= target_part | '(' target_part ')' | '(' target_part (',' target_part)+ ')'
target_part    ::= 
target_wild    ::= '_'
into           ::= "into" '{' into_vars '}' 
into_vars      ::= into_var ( into_var )*
into_var       ::= in_scope | in_var 
in_scope       ::= identifier ';' 
in_var         ::= identitfier ":=" expr ';'
when           ::= "when" predicate
predicate      ::= 
result         ::= "=>" expr ';'
expr           ::=

How the your code looks when using the form being proposed is clean looking and fit in well is existing C# style. It's not too far from existing style to be jarring, when creating the examples it felt (to me) very natural.

@AdamSpeight2008
Copy link
Contributor Author

An attempt at parsing the simple grammar in #216

/* Digit ::= '0' - '9'  */
Func<char,bool> Digit = (c)=>{
                               match ( c ) with 
                               { 
                                 | '0'-'9' =>  true.
                                 |  _      => false;
                               }
                             };
/* Separator ::= '_'  */
Func<char,bool> Separator = c =>{ 
                                  match ( c ) with
                                  { | '_' =>  true;
                                    |  _  => false;
                                  }
                                }
/* Literal ::= Digit ( Separator? Digit)* */
match (curr) with
{ 
  | _ when Digit => { var peek = curr;
                      do 
                      {
                        peek = peek.Next();
                      } until ( match (peek) with
                                {
                                  | _ when Separator => { var peek2 = peek.Next();
                                                          match (peek2) with
                                                          { | _ when Digit => {
                                                                                peek = peek2;
                                                                                return true;
                                                                              }
                                                            | _            => false;
                                                          }
                                                        }
                                  | _ when Digit     =>  true;
                                  | _                => false;
                                }
                              )
                    }
  | _            =>;
}

I don't it look that bad.

@AdamSpeight2008
Copy link
Contributor Author

Usng the match slightly differently.

match ( curr ) 
{ | _ when Digit  => var peek1 = curr;
                     var peek2 = curr;
                     do
                     {
                       peek1 = peek1.Next()
                       peek2 = peek1.Next()
                       match ( peek1 , peek2 )
                       { | (_,_) when ( Separator , Digit ) => peek1 = peek2;
                         | (_,_) when ( Digit     ,   _   ) => peek1 = peek1;
                         | _                                => exit do;
                       }
                     };
  | _             =>  ;
}

By some for a prefix it could be little smaller.

match ?? ( curr ) 
{ | Digit  => var peek1 = curr;
              var peek2 = curr;
              do
              {
                peek1 = peek1.Next()
                peek2 = peek1.Next()
                match ?? ( peek1 , peek2 )
                { | ( Separator , Digit ) => peek1 = peek2;
                  | ( Digit     ,   _   ) => peek1 = peek1;
                  | ( _ , _ )             => exit do;
                }
              };
  | _      =>  ;
}

VB.net Example

Dim Digit As Func(Of Char,Boolean) = Function( c )
                                       Match ( c )
                                         Case "0"c To "9"c : Return True
                                         Case Else
                                       Return False
                                     End Function

Dim Separator As Func(Of Char,Boolean) = Function( c )
                                           Match ( c )
                                             Case "_" : Return True
                                             Case Else
                                              Return False
                                           End Math
                                         End Function
Match (curr)
  Case _ When Digit
    Dim peek = curr
    Do
      peek = peek.Next()
    Loop Until Match (peek )
                 Case _ When Separator 
                   Dim peek2 = peek.Next()
                   Match (peek2) 
                     Case _ When Digit : peek = peek2 : Return True
                     Case Else
                       Return False
                   End Match
                 Case _ When Digit : Return True
                 Case Else
                   Return False
               End Match     
  Case Else
End Match



Match ( curr ) 
  Case _ When Digit 
    Dim peek1, peek2 = curr
    Do
      peek1 = peek1.Next
      peek2 = peek1.Next
      Match ( peek1 , peek2 )
        Case ( _ , _ ) When ( Separator , Digit ) : peek1 = peek2
        Case ( _ , _ ) WHen ( Digit     ,   _   ) : peek1 = peek1
        Case Else
          Exit Do
      End Match
    Loop Until False
  Case Else
End Match

@danfma
Copy link

danfma commented Mar 12, 2015

I like your idea but is something like Nemerle does and to be honest, I can lost myself easily on something like this:

match ( e , a ) with
{  
  | ( null, null ) => ... ;
  | ( _ , _ ) into 
       {l:= a.Left as IdentifierName;
        r:= a.Right as IdentifierName;
       } => match (l,r) with
            {
              | (null,null) =>  ... ;
              | ( _ , _ ) when (a.Name.name == r.Name.name) => ... ;
              | _ => ... ;
            };
  | _ => ... ;
}

But I agree with you, it's powerfull!

@GeirGrusom
Copy link

What's the point if the with identifier terminal? The pipe operator and underscore seems completely out of place in C#.

@AdamSpeight2008
Copy link
Contributor Author

@GeirGrusom The syntax is from Nemerle.

  • match isn't an identifier, like if and else aren't, it helps delimitate the parts of the match expression
    I think of match (this) with {these} I didn't know the reason why it's there, My thoughts are, is that it disambiguates the grammar. In the later example I don't use it.
  • The I define the start of match clause, it's a lot easier to write than case:
  • The _ is more like a wildcard, match against anything.

@GeirGrusom
Copy link

Sorry I meant terminal. with seems completely pointless. It doesn't solve ambiguities. It's just syntax noise.

I think in order to look more like C# it should use case instead of |, and default instead of | _.

@gafter
Copy link
Member

gafter commented Apr 23, 2015

This proposal doesn't stand on its own; it is discussion of an alternative syntax for a proposal already under consideration. Please move discussion to #1572 or #206 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants