where clause

bearophile bearophileHUGS at lycos.com
Sun Mar 6 17:23:02 PST 2011


I have discussed this topic already once in past, but I want to talk some more about it. Lately I am using Haskell a bit, and I'm appreciating this very simple feature. In D it's not as useful as in Haskell because D allows nested functions, that are one of its main purposes, but it's a cute thing.

The "where" allows to write an expression where some parts of it are defined below it. In the where you are allowed to put one or more variables (immutable values in Haskell) and functions (values again).

So first of all some usage examples from random Haskell code (Haskell uses significant indentation, almost as Python):


median xs | even len  = (mean . take 2 . drop (mid - 1)) ordered
          | otherwise = ordered !! mid
  where len = length xs
        mid = len `div` 2
        ordered = sort xs


pts n = 
  map (map (intPoint.psPlus (100,0)). ((0,300):). scanl1 psPlus. ((r,300):). zipWith (\h a -> (h*cos a, h*sin a)) rs) hs
  where
    [r,h,sr,sh] = [50, pi/5, 0.9, 0.75]
    rs   = take n $ map (r*) $ iterate(*sr) sr
    lhs  = map (map (((-1)**).fromIntegral)) $ enumBase n 2
    rhs  = take n $ map (h*) $ iterate(*sh) 1
    hs   = map (scanl1 (+). zipWith (*)rhs) lhs


levenshtein s1 s2 = last $ foldl transform [0 .. length s1] s2
  where transform ns@(n:ns') c = scanl compute (n+1) $ zip3 s1 ns ns'
          where compute z (c', x, y) = minimum
                    [y+1, z+1, x + fromEnum (c' /= c)]



drawTree (width, height) start steps stdgen = do
    img <- image width height off
    setPix img (Pixel start) on
    gen <- newSTRef stdgen
    let -- randomElem :: [a] -> ST s a
        randomElem l = do
            stdgen <- readSTRef gen
            let (i, stdgen') = randomR (0, length l - 1) stdgen
            writeSTRef gen stdgen'
            return $ l !! i
        -- newPoint :: ST s (Int, Int)
        newPoint = do
            p <- randomElem border
            c <- getPix img $ Pixel p
            if c == off then return p else newPoint
        -- wander :: (Int, Int) -> ST s ()
        wander p = do
            next <- randomElem $ filter (inRange pointRange) $ adjacent p
            c <- getPix img $ Pixel next
            if c == on then setPix img (Pixel p) on else wander next
    replicateM_ steps $ newPoint >>= wander
    stdgen <- readSTRef gen
    return (img, stdgen)
  where pointRange = ((0, 0), (width - 1, height - 1))
        adjacent (x, y) = [(x - 1, y - 1), (x, y - 1), (x + 1, y - 1),
                           (x - 1, y),                 (x + 1, y),
                           (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)]
        border = liftM2 (,) [0, width - 1] [0 .. height - 1] ++
                 liftM2 (,) [1 .. width - 2] [0, height - 1]
        off = black
        on = white



brkdwn = takeWhile (not.null) . unfoldr (Just . second (drop 1) . span ('$'/=))
 
format j ls = map (unwords. zipWith align colw) rows  
  where
    rows = map brkdwn $ lines ls
    colw = map (maximum. map length) . transpose $ rows
    align cw w =
      case j of
        'c' -> (replicate l ' ') ++ w ++ (replicate r ' ')
        'r' -> (replicate dl ' ') ++ w
        'l' -> w ++ (replicate dl ' ')
        where
           dl = cw-length w
           (l,r) = (dl `div` 2, dl-l)


maze :: Int -> Int -> StdGen -> ST s Maze
maze width height gen = do
    visited <- mazeArray False
    rWalls <- mazeArray True
    bWalls <- mazeArray True
    gen <- newSTRef gen
    liftM2 (,) (rand (0, maxX) gen) (rand (0, maxY) gen) >>=
        visit gen visited rWalls bWalls
    liftM2 Maze (freeze rWalls) (freeze bWalls)
  where visit gen visited rWalls bWalls here = do
            writeArray visited here True
            let ns = neighbors here
            i <- rand (0, length ns - 1) gen
            forM_ (ns !! i : take i ns ++ drop (i + 1) ns) $ \there -> do
                seen <- readArray visited there
                unless seen $ do
                    removeWall here there
                    visit gen visited rWalls bWalls there
          where removeWall (x1, y1) (x2, y2) = writeArray 
                    (if x1 == x2 then bWalls else rWalls)
                    (min x1 x2, min y1 y2)
                    False
 
        neighbors (x, y) = 
            (if x == 0    then [] else [(x - 1, y    )]) ++
            (if x == maxX then [] else [(x + 1, y    )]) ++
            (if y == 0    then [] else [(x,     y - 1)]) ++
            (if y == maxY then [] else [(x,     y + 1)])
 
        maxX = width - 1
        maxY = height - 1
 
        mazeArray = newArray ((0, 0), (maxX, maxY))
            :: Bool -> ST s (STArray s (Int, Int) Bool)
 
printMaze :: Maze -> IO ()
printMaze (Maze rWalls bWalls) = do
    putStrLn $ '+' : (concat $ replicate (maxX + 1) "---+")
    forM_ [0 .. maxY] $ \y -> do
        putStr "|"
        forM_ [0 .. maxX] $ \x -> do
            putStr "   "
            putStr $ if rWalls ! (x, y) then "|" else " "
        putStrLn ""
        forM_ [0 .. maxX] $ \x -> do
            putStr "+"
            putStr $ if bWalls ! (x, y) then "---" else "   "
        putStrLn "+"
  where maxX = fst (snd $ bounds rWalls)
        maxY = snd (snd $ bounds rWalls)



import System.Random (StdGen, getStdGen, randomR)
 
trials :: Int
trials = 10000
 
data Door = Car | Goat deriving Eq
 
play :: Bool -> StdGen -> (Door, StdGen)
play switch g = (prize, new_g)
  where (n, new_g) = randomR (0, 2) g
        d1 = [Car, Goat, Goat] !! n
        prize = case switch of
            False -> d1
            True  -> case d1 of
                Car  -> Goat
                Goat -> Car
 
cars :: Int -> Bool -> StdGen -> (Int, StdGen)
cars n switch g = f n (0, g)
  where f 0 (cs, g) = (cs, g)
        f n (cs, g) = f (n - 1) (cs + result, new_g)
          where result = case prize of Car -> 1; Goat -> 0
                (prize, new_g) = play switch g
 
main = do
    g <- getStdGen
    let (switch, g2) = cars trials True g
        (stay, _) = cars trials False g2
    putStrLn $ msg "switch" switch
    putStrLn $ msg "stay" stay
  where msg strat n = "The " ++ strat ++ " strategy succeeds " ++
            percent n ++ "% of the time."
        percent n = show $ round $
            100 * (fromIntegral n) / (fromIntegral trials)


As you see you are also allowed to nest where statements, as here:

cars n switch g = f n (0, g)
  where f 0 (cs, g) = (cs, g)
        f n (cs, g) = f (n - 1) (cs + result, new_g)
          where result = case prize of Car -> 1; Goat -> 0
                (prize, new_g) = play switch g


A possible D syntax, using a new "where" keyword:


do {
    int y = x + 5;
} where {
    auto x = foo();
}


Advantages:
- It allows a better top-down decomposition of the code, that sometimes is more natural and clear. You are able to express a complex formula at high level first, and give its details into the where clause. So it inverts the order of code, sometimes such code is simpler to understand.
- It helps to keep the namespace clean. In this example at the end only y is present in the namespace because x was created in a nested temporary namespace.


As in the do-while the brackets are optional if you have just one statement, but this is not good coding style:

do auto y = x * x + x; where auto x = foo();


Another usage example, showing a locally defined function:

do {
    auto r = map!(sqr)(items);
} where {
    int sqr(int x) pure nothrow {
        return x * x;
    }
}

In D you are able to simplify a complex function moving some of its code into smaller functions that you are allowed to put inside the original complex function (they are allowed to be pure and static too, if you want safety and more performance), this replaces some of the Haskell purposes of "where", but not all of them.

Haskell programmers don't criticize this where syntax (as they do with infix function syntax I've shown before).


If you don't want to add a new keywork, this is a alternative syntax:

do {
    int y = x + 5;
} with {
    auto x = foo();
}

Bye,
bearophile


More information about the Digitalmars-d mailing list