haskelloperator-precedenceghci

How can I quickly tell how Haskell parses an expression? Is it possible to print the expression with parenthesis around every non-leaf AST node?


Sometimes I run into expressions full of different operators and I have no idea how they get parsed. I know that I can ask GHCi information about an operator with :i, but having to look at multiple operators and apply their precedence and associativity to reconstruct it in my mind feels quite hard.

Is there any simpler way to understand it?

Example

For example take this valid Haskell expression:

7 ¤ 2 ⏆ 4 ⏆ 3 ¤ 1 ♂ 3 ♂ 2

And assume that the precedence of those operators is:

Question

Is it possible to ask GHCi to print it out like this?

(7 ¤ ((2 ⏆ 4) ⏆ 3)) ¤ (1 ♂ (3 ♂ 2))

Are there any other ways to quickly get an idea of how that expression works?


Solution

  • You can define an expression type, use those operators to construct an expression, and pretty-print it.

    -- E.hs
    module E where
    
    data Exp
      = Leaf String
      | Binop Exp String Exp
    
    pretty :: Exp -> String
    pretty (Leaf l) = l
    pretty (Binop l o r) = "(" ++ pretty l ++ " " ++ o ++ " " ++ pretty r ++ ")"
    
    instance Show Exp where
      show = pretty
    
    type B a = a -> a -> a
    
    binop :: String -> B Exp
    binop o l r = Binop l o r
    
    instance Num Exp where
      (+) = binop "+"
      (-) = binop "-"
      (*) = binop "*"
      fromInteger = Leaf . show  -- interprets literals as expressions
    
    infixl 6 ¤
    infixl 7 ⏆
    infixr 8 ♂
    
    (¤), (⏆), (♂) :: B Exp
    (¤) = binop "¤"
    (⏆) = binop "⏆"
    (♂) = binop "♂"
    
    example :: Exp
    example = 7 ¤ 2 ⏆ 4 ⏆ 3 ¤ 1 ♂ 3 ♂ 2
    
    $ ghci E.hs
    > 7 ¤ 2 ⏆ 4 ⏆ 3 ¤ 1 ♂ 3 ♂ 2
    ((7 ¤ ((2 ⏆ 4) ⏆ 3)) ¤ (1 ♂ (3 ♂ 2)))
    

    With the above solution, you have to re-define all operators, looking up and copying their precedence levels manually.

    Another way is to use Template Haskell to get the actual AST and pretty-print it.

    -- T.hs
    {-# LANGUAGE TemplateHaskell #-}
    module T where
    
    import Language.Haskell.TH
    
    pretty :: Exp -> String
    pretty (LitE l) = pprint l
    pretty (InfixE (Just x) (VarE n) (Just y)) = "(" ++ pretty x ++ " " ++ nameBase n ++ " " ++ pretty y ++ ")"
    pretty e = error ("pretty: unhandled " ++ show e)
    
    prettify :: Q Exp -> Q Exp
    prettify q = [| putStrLn |] `appE` fmap (LitE . StringL . pretty) q
    

    Make sure to enable the TemplateHaskell extension.

    $ ghci -XTemplateHaskell E.hs T.hs
    > :m E T
    > $(prettify [| 7 ¤ 2 ⏆ 4 ⏆ 3 ¤ 1 ♂ 3 ♂ 2 |])
    ((7 ¤ ((2 ⏆ 4) ⏆ 3)) ¤ (1 ♂ (3 ♂ 2)))