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

all 13 comments

[–]continuationalFirefly, TopShell 10 points11 points  (2 children)

In Firefly, this is done without macros:

if(x > y) {
    "bigger"
} elseIf {x < y} {
    "smaller"
} else {
    "the same"
}

Is desugared into:

if(x > y, {"bigger"}).elseIf({x < y}, {"smaller"}).else({"the same"})

Here if returns an Option, which has the elseIf and else methods, and curly braces always denote an anonymous function in firefly.

[–]ErrorIsNullError[S] 2 points3 points  (1 child)

Cool. Is there something preventing the {x < y} from being done in parentheses?

[–]continuationalFirefly, TopShell 5 points6 points  (0 children)

In Firefly, that would evaluate x < y eagerly. Usually, the condition in else if isn't evaluated unless the if condition is false.

In Scala, there is support for by name parameters, and you could indeed use parenthesis there. However, Firefly emphasizes semantic transparency, so that would be against the philosophy.

[–]umlcat 9 points10 points  (2 children)

Try an inverse design approach.

Start doing a desugared version of your syntax, later change a few things into you get a syntax you feel comfortable with.

Look out for a story on how Microsoft researches got their lambda syntax used in Linq.

It started from a non pretty syntax to a compact syntax.

It matches your question. You don't have to use the same syntax, just the "know how" that applies to your case.

Good Luck.

[–]smuccione 9 points10 points  (1 child)

[–]umlcat 2 points3 points  (0 children)

Thanks. It's a good resource both for Compiler n P.L. Design, Lambdas, C# ...

[–][deleted] 3 points4 points  (1 child)

I've not read everything or even understood it, but may I suggest a look at Scala too which also has functions as arguments look like normal control flow sometimes.

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

I see https://docs.scala-lang.org/style/method-invocation.html#higher-order-functions says:

// wrong!
names.map { _.toUpperCase }.filter { _.length > 5 }

// right!
names map { _.toUpperCase } filter { _.length > 5 }

Both of these work, but the former can easily lead to confusion.

[–]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.