Search code examples
haskellcompiler-construction

Nested "where" or "let in" CodeStyle in Haskell


I faced with some issue in Haskell: code-style and big functions (I'm continuing to learn Haskell through writing toy-language).

I have some necessary-big-function (see example). And there are two sub-function (nested where) in it. And there is no reason to place its sub-finction in module scoupe. How "Haskell code-style" or "Haskell code-style best practics" suggest to solve this problem of "ungraceful and clumsy code"?

Function (with later comment):

-- We can delete (on DCE) any useless opers.
-- Useful opers - only opers, whitch determine (directly or transitivery) result of GlobalUse oper
addGlobalUsageStop :: [Var] -> IR -> IR
addGlobalUsageStop guses iR = set iOpers (ios ++ ios') $ set opN opn' iR
    where
    ios = _iOpers iR
    gdefs = _gDefs iR :: M.Map Int Var
    opn = _opN iR
    guses' = nub $ filter isRegGlobal guses
    ogs = catMaybes $ map (concatIOperWithGDef gdefs) $ reverse ios
        where
        concatIOperWithGDef gdefs' (i, o) = case gdefs' M.!? i of
            Nothing -> Nothing
            Just gd -> Just (o, gd)
    nops = newGUses ogs guses'
        where
        newGUses [] _ = []
        newGUses _ [] = []
        newGUses ((Oper _ d _ _, g):os) guses = if elem g guses
            then (Oper GlobalUse g (RVar d) None):newGUses os (filter (g /=) guses)
            else newGUses os guses
    ios' = zip [opn..] nops
    opn' = opn + length ios'  

Notices:

  1. If you want to know why I even wrote such big function the answer is: because this is some big (and one-needed functionality in compiler): - for each "returning variable" we shoul find last operation, witch defines it (actually corresponding virtual register), and expand our IR with constructed opers.

  2. I'v seen some similar questions: Haskell nested where clause and "let ... in" syntax but they are about "how typewrite correct code?", and my question "is this code Code-Style correct, and if it isn't - what should i do?".


Solution

  • The question is a good one, but the example code isn't a great example. For me, the correct fix in this particular case is not to talk about how to stylishly nest wheres; it's to talk about how to use library functions and language features to simplify the code enough that you don't need where in the first place. In particular, list comprehensions get you very far here. Here's how I would write those two definitions:

    import Data.Containers.ListUtils (nubOrdOn)
    
    ... where
        ogs = [(o, gd) | (i, o) <- reverse ios, Just gd <- [gdefs M.!? i]]
        nops = nubOrdOn fun
            [ Oper GlobalUse g (RVar d) None
            | (Oper _ d _ _, g) <- ogs
            , g `elem` guses'
            ]
        fun (Oper _ g _ _) = g -- this seems useful enough to put at the global scope; it may even already exist there
    

    Since ogs isn't mentioned anywhere else in the code, you might consider inlining it:

        -- delete the definition of ogs
        nops = nubOrdOn fun
            [ Oper GlobalUse g (RVar d) None
            | (i, Oper _ d _ _) <- reverse ios
            , Just g <- [gdefs M.!? i]
            , g `elem` guses'
            ]