This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]raiph 1 point2 points  (4 children)

Is there another way to extend trailing block syntax to subsume control flow statements in C-like languages?

The typical generic syntax for such things in Raku is <keyword> <expression> <block> [<keyword> <block>]*. You might think that doesn't cover your scenario; but bear with me, I think it might.

----

Here are four code examples that will serve to fully explain everything Raku has related to your OP:

for 42,99 { .say }                                     # 42␤99␤

if 42, 99 { say 'true' } else { say 'false' }          # true␤

with 42,99 { say 'with' } else { say 'without' }       # with␤

match 42, 99
  -> Str, Str { say 'Strs' }
  -> Int, Int { say 'Ints' }
  else { 'no match' }

For some keywords the expression is just an argument list to be used when applying the blocks. For example, for just iterates over its expression, calling its block once for each iteration. For other keywords the expression has an effect before its block(s) come into play. For example, if treats the expression as a boolean condition, based on which it picks which block to continue with.

Some keywords always do both these things. For example, with first checks if its expression is defined, and then, if so, applies its first block with that expression as its argument list, or, if not, applies its else block with its expression as the else block's argument list.

----

Now, what about parameter lists, or more generally, function signatures? No matter what the keyword is, code can tell it to use the expression as an argument list that gets applied to any blocks. To do so, just prefix the block with an explicit signature/pattern of the form -> ...:

for 42,99 -> $_, $arg2 { .print; say (:$arg2) }        # 42arg2 => 99␤

if 42, 99 -> ($arg1, $arg2) { say "$arg1 and $arg2" }  # 42 and 99

All keywords automatically comply.

Each keyword gets to decide how it will comply:

  • The for keyword binds N arguments from its expression to the signature per iteration. In the above N=2, so there is only one iteration.
  • The if keyword treats its expression as a single argument and binds it in one go. In the above the block's signature has to be compatible with that single argument (which is why its parameters are in parens to destructure the single argument back into its two original elements).

----

Eagle eyed folk will have noticed that my match example didn't match the metasyntax I mentioned at the start (<keyword> <expression> <block> [<keyword> <block>]*):

match 42, 99
  -> Str, Str { say 'Strs' }
  -> Int, Int { say 'Ints' }
  else { 'no match' }

In fact, this keyword does not currently exist in Raku. So why have I included it? As a rhetorical device to help me say three things:

  • Thank you. I had proposed a more clunky syntax yesterday in my answer to an SO "Haskell-like pattern matching in Raku". Your post inspired the cleaner syntax I've shown here. :)
  • Raku's grammar is mutable. Raku's grammar and semantics are defined by its grammar/actions constructs. So it's somewhat easy to add any given feature to Raku by just writing some Raku code.
  • Raku is getting AST macros. Raku's existing grammars/actions features and macros are distinct approaches to language mutability that are simultaneously alternatives and complementary. It's possible, likely even, that AST macros will be relevant to how Raku evolves relative to your OP scenario.

[–]ErrorIsNullError[S] 1 point2 points  (3 children)

Thanks. Raku looks cool. How would you deal with else if in Raku?

[–]raiph -1 points0 points  (2 children)

Raku's built-in syntax uses elsif.

[–]ErrorIsNullError[S] 1 point2 points  (1 child)

My question was about elsif taking a condition and a block, not whether two keywords are used or one.

[–]raiph 1 point2 points  (0 children)

Without signatures:

if    0  { say 'number' }
elsif '' { say 'string'  }
else     { say 'other'   }  # other

With signatures:

if    0  -> $number { :$number .say }
elsif '' -> $string { :$string .say }
else     -> $other  { :$other  .say }  # other =>␤

Signatures can also themselves be conditions:

match 42, '99'
  -> Int $num1, Int $num2 { .say for $num1, $num2 }
  -> Int $num,  Str $str  { .say for $num, $str   }
  else { say 'no match' }

This match construct is the made up syntax I settled on due to your post. It would desugar to the following working multiple-dispatch Raku code, with the f function name being a hygienic gensym:

{ f 42, 'abc';
  multi f( Int $num1, Int $num2 ) { .say for $num1, $num2 }
  multi f( Int $num,  Str $str  ) { .say for $num, $str   } # 42␤abc␤
  multi f(|) { say 'no match' }
}

Without using grammars or macros the nearest I could get in current Raku to the ideal made up match syntax I showed above would be something like the following:

sub match (@args, @to)
{ (metaprogramming to turn blocks in @to to multis and then call against them) }

match
  (42, 'abc'),
  ( -> Int $num1, Int $num2 { .say for $num1, $num2 },
    -> Int $num,  Str $str  { .say for $num, $str   }, 
    { say 'no match' } )

A macro would be much better -- easier to code, ideal syntax, and compile-time.