简体   繁体   中英

Haskell extension for function indentation syntax

Apologies for the vague question title; I could't figure out the right one.

A few years ago I ran into a page (on the Haskell Prime wiki, I guess) of a language extension proposal. Suppose a Haskell code like:

f1 (f2 v1 v2 v3) (f3 v4 v5 v6)

and let's say the f2 v1 ... part has long function names and value names, making the whole line a lot longer than desirable and harder to read. We would want to change it into

f1
    (f2 v1 v2 v3)
    (f3 v4 v5 v6)

which is more readable and would represent the structure a lot more clearly, ie what is argument for what. But then, if we have a more complex relation like

f1
    (f2 (f3 v1) (f4 v2) v3)
    (f5 v4)
    (f7 v5)

then it gets untidy very quickly and parentheses get out of control.

Ideally we could

f1
    f2
        f3 v1
        f4 v2
        v3
    f5 v4
    f7 v5

So the structure is clear for both human and machine. But since it was impossible with the Haskell standard, a bunch of smart guys came up with the idea of a language extension to allow a new operator. The operator looked like $ but had a different associativity. Let's say it's called $$ , then:

f1 $$
    f2 v1 v2 v3
    f3 v4 v5 v6

would be equivalent to f1 (f2 v1 v2 v3) (f3 v4 v5 v6) and

f1 $$
    f2 $$
        f3 v1
        f4 v2
        v3
    f5 v4
    f7 v5

would translate to f1 (f2 (f3 v1) (f4 v2) v3) (f5 v4) (f7 v5) .

I'd love to use a GHC extension for this feature because implementing a DSL with a tree-like structure would be a breeze with it, but I couldn't locate any resource at all.

  1. What is this concept (and the operator I called $$ ) called?
  2. Is there already a GHC extension for this?
    • If there isn't, I'd really appreciate links to any other related extension. I am willing to learn how to write a GHC extension to implement this.

Edit : I know there are many workarounds, including Template Haskell QQ and parentheses, but I remember seeing a language extension proposal and my question is about that extension, not about the other ways of addressing the problem.

By the way, I feel very reluctant to consider parentheses an option, because parenthesizing a complex nested structure will make it look very cluttered and defeat the whole point of indent-based code layout. The benefit of "off-side rule" syntax over "curly brackets" syntax is compromised if you have to parenthesize just about everything and manually style it to look structured. I am pretty sure there are many Haskellers who consider it unclean, as observed in monoid-based libraries having a meaningless instance of monad (and an empty value to pass around) to exploit the do syntax. While they can write

example = mappend
    [ lorem ipsum
    , dolor amet
    ]

they prefer to declare a monad instance and write

example = do
    lorem ipsum
    dolor amet

not because it makes more sense type-wise but because it just looks clean and concise. (In fact the issue is worse with parentheses than with brackets because you need to insert almost twice as many special characters to represent the structure of function application.) It would also drastically reduce the chance of missing symbols by mistake, which is exactly the advantage of off-side rule languages over curly bracket languages. In the best scenario, missing or misplacing parentheses, brackets, or commas will result in a compile-time error and worst, a faulty program that does something wild and unintended.

I finally managed to find the page so I'll answer my own question.

It's called Application Layout . It was proposed after the monoid layout, which suggests a new keyword be (or mdo ) that would eliminate the need of meaningless monad instances to get the do notation.

If you want to embed your custom syntax in GHC Haskell, you could try Quasiquoters .

Shamelessly copying from the linked examples, quasiquoting can turn syntax like

[expr|1 + 3|]

into

BinopExpr AddOp (IntExpr 1) (IntExpr 3)

where

data Expr  =  IntExpr Integer
           |  AntiIntExpr String
           |  BinopExpr BinOp Expr Expr
           |  AntiExpr String
data BinOp  =  AddOp |  SubOp |  MulOp |  DivOp

You do need to build your own parser turning strings such as "1 + 3" into the actual term representation. In your case, you will need to deal with indentation on your own.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM