Skip to content

Instantly share code, notes, and snippets.

@chrisdone
Last active July 24, 2017 23:47
Show Gist options
  • Save chrisdone/d9d33e4770a2fef19ad1 to your computer and use it in GitHub Desktop.
Save chrisdone/d9d33e4770a2fef19ad1 to your computer and use it in GitHub Desktop.
Proposal: InfixExpressions language extension

Proposal: InfixExpressions language extension

Motivation

It's common for Haskellers to have combining functions where the arguments to each combinator changes the overall type. Writing out combine x (combine y z) gets old, and the heterogenous types means that we can't use a simple foldr/foldl, so we invent e.g. x % y % z. The problem with this for me is that it introduces its own redundancy and viewing/editing overhead.

Lispers are used to writing (* x y z) and I see this as a reasonable and predictable syntactic improvement. For Lispers like me, being able to just write an identifier out (like combine) and read that in plain English has always been something I wish was more catered to in Haskell. As far as syntax goes, there is a lot of Perl-like influence in the Haskell world, and I generally try to contribute to the Lisp-like side of that balance.

Proposal

Suppose we had a keyword like infix—in fact, we do! It's already taken up at the declaration level, but it's only disabled at the expression level, not used. Parsers don't allow this to be used as an identifier, and editors syntax highlight it as a keyword. So supposing that we define a variadic infix keyword which accepts an identifer and arguments:

infix <op>  (<op>)
infix <op> a  (a <op>)
infix <op> a b c d  a <op> b <op> c <op> d
infix name a  (a `name`)
infix name a b c d  a `name` b `name` c `name` d

In other words, infix takes the following identifier and expands it to an infix interspersal. The left/right precedence will be determined later by whatever has been declared for that operator or name in the usual way. (infixl/infixr could also be supported, but it'd probably be more confusing to read nonce fixities.)

The precedence of this is determined like regular function application, so:

infix <op> arg1 arg2 % 

is equiv. to

(infix <op> arg1 arg2) % 

The spaces bind tighter than any other operators.

Three kinds of use-cases

  • Some things are foldable cleanly like Monoid instances, so you can just mconcat [x,y,z], but that will not be inlined in GHC 7.8.4.
  • Other things like x <|> y is not the same as asum [x,y] due to the additional empty being introduced. You can also use foldl1 kind of functions, but they are partial and therefore not desirable.
  • Finally, things like <*>, $=, $, ., #/:& (e.g. in HList/vinyl) can't be folded at all, because the types are different.

The third use-case doesn't have a solution that I'm aware of. So this solves that. It also solves the second use-case, which has only a partial (he he) solution. The first use-case is just a bonus.

Examples

Monoids

Instead of:

foo <> bar <> mu <> zot

You can write:

infix <> foo bar mu zot
infix <> "Your name is " name " and your age is" age

You often see people using a writer monad to acheive this kind of convenience.

or

infix mappend foo bar mu zot

foo `mappend` bar `mappend` mu `mappend` zot

Monads/Applicatives

Instead of:

foo <*> bar <*> mu <*> zot

you can write:

infix ap foo bar mu zot

foo `ap` bar `ap` mu `ap` zot

Instead of:

foo <|> bar <|> mu <|> zot

you can write:

infix <|> foo bar mu zot

Others

Conduits:

infix $= source foo bar mu zot $$ sink

Composition:

infix . foo bar mu zot

Convenience for application:

infix $ head group sort [1..5]

If desired there could possibly be special support for tuple's comma:

infix , a b c d e

Lists can be funnily expressed too:

infix : a b c d e []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment