www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - where clause

reply bearophile <bearophileHUGS lycos.com> writes:
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
Mar 06 2011
next sibling parent reply Russel Winder <russel russel.org.uk> writes:
On Sun, 2011-03-06 at 20:23 -0500, bearophile wrote:
[ . . . ]

I wonder if you may have misunderstood the reason for the where clause
in functional languages such as Haskell and ML (usually OCaml).  In
these languages the body of a function must be a single value-returning
expression.  This means there has to be a separate clause for all the
declarations of the single-assignment variables -- caveat the use of
monads

 do {
     auto r =3D map!(sqr)(items);
 } where {
     int sqr(int x) pure nothrow {
         return x * x;
     }
 }
What's wrong with: { auto sqr =3D function int ( int ) { return x * x ; } ; auto r =3D map ! ( sqr ) ( items ) ; } Seems idiomatic and straightforward with less noise?
 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.
In Python, line 5 of the PEP-20 "The Zen of Python" reads: Flat is better than nested. there are good reasons for this. Of course lines 8 and 9 are: Special cases aren't special enough to break the rules. Although practicality beats purity. so there are times when you do nest functions -- basically to make closures very analogously to the way you seem to have to in D.
 Haskell programmers don't criticize this where syntax (as they do with
 infix function syntax I've shown before).
Because they have no choice. PS I have begun to dislike languages that use semi-colon as a statement terminator! (It took me 4 attempts to get my two line example to compile :-(( --=20 Russel. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D Dr Russel Winder t: +44 20 7585 2200 voip: sip:russel.winder ekiga.n= et 41 Buckmaster Road m: +44 7770 465 077 xmpp: russel russel.org.uk London SW11 1EN, UK w: www.russel.org.uk skype: russel_winder
Mar 07 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Russel Winder:

I wonder if you may have misunderstood the reason for the where clause in
functional languages such as Haskell and ML (usually OCaml).  In these
languages the body of a function must be a single value-returning expression. 
This means there has to be a separate clause for all the declarations of the
single-assignment variables -- caveat the use of monads<
Right, but the point of my post was that a where statement is able to give some advantages to D too.
 What's wrong with:
 
         {
           auto sqr =3D function int ( int ) { return x * x ; } ;
           auto r =3D map ! ( sqr ) ( items ) ;
         }
 
 Seems idiomatic and straightforward with less noise?
Both my code and this code are kind of useless because their variables will not be accessible when the block ends. So my syntax is not good :-(
 so there are times when you do nest functions -- basically to make
 closures very analogously to the way you seem to have to in D.
Nesting functions is very useful. Python Zen is not a list of absolute laws :-)
 PS  I have begun to dislike languages that use semi-colon as a statement
 terminator!  (It took me 4 attempts to get my two line example to
 compile :-((
I like Python but I have found some Haskell code on web pages, that I copy and try to run. I am having many problems caused by tabs present in the original code that vanish in HTML and break haskell indentations, creating bugs. So be careful what you wish for. From the web interface your posts are often empty/invisible: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=131443 Bye, bearophile
Mar 07 2011
parent spir <denis.spir gmail.com> writes:
On 03/07/2011 12:28 PM, bearophile wrote:
 I like Python but I have found some Haskell code on web pages, that I copy and
try to run. I am having many problems caused by tabs present in the original
code that vanish in HTML and break haskell indentations, creating bugs. So be
careful what you wish for.


  From the web interface your posts are often empty/invisible:
 http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=131443
That's a design issue with tools which do not respect the user; considering as default him/her as stupid and helpless. Like most wiki-languages as well. (Like Unix's general philosophy, troll.) Like email readers which do not allow one setting TAB's width, so that one cannot separate semantics marks (tab) from presentation (indent width) and is forced to insert n spaces (no model/view possible in doc writing) instead --> thus ever reader has to do with my indent preferences. Denis -- _________________ vita es estrany spir.wikidot.com
Mar 07 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 03/07/2011 02:23 AM, bearophile wrote:
 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
Hello Bearophile, I also find this feature very nice (to the point of having asked for its introduction in Python, one day --without success). I like it because it makes the reasoning clear. There are 2 kinds of statement sequences in declarative programming: * (chrono)logical sequences (*) * expression sequences The latter define sub-expressions of a more complex super-expression -- if only to make it clear -- using temporary variables. In functional programming, using temp that way vars in at best awkward; rather unnatural and unpractical. Thus, 'where' (also 'let' in scheme/Lisp) constructs really fulfill a need. In imperative programming, you don't have such a need. But conceptualy, a way to express that sub-expressions are part of a higher one would be very helpful, at least to make a difference with logical sequences of statements. This would also encourage people writing clearer code instead of constructing unlegible expressions (esp. in C-like languages). But there is a simple workaround: just use a comment, saying the following statements construct a super expression. import std.algorithm, std.array; T[][2] halves (T) (T[] a) { // find median index (artificial example, indeed) auto len = a.length; auto median = len / 2; // a logical step: allows not computing 'sorted' twice auto sorted = array(a.sort()); // get halves slices auto smalls = sorted[0..median]; auto bigs = sorted[median..sorted.length]; return [smalls,bigs]; } unittest { int[] ints = [3,0,4,9,6,5,8,7,1,2]; assert( halves(ints) == [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]] ); } void main () {} Denis (*) typically, ordered because they change program state --but there are other reasons: see example -- _________________ vita es estrany spir.wikidot.com
Mar 07 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 03/07/2011 09:26 AM, Russel Winder wrote:
 On Sun, 2011-03-06 at 20:23 -0500, bearophile wrote:
 [ . . . ]

 I wonder if you may have misunderstood the reason for the where clause
 in functional languages such as Haskell and ML (usually OCaml).  In
 these languages the body of a function must be a single value-returning
 expression.  This means there has to be a separate clause for all the
 declarations of the single-assignment variables -- caveat the use of
 monads
I know about nothing of Haskel and ML languages, but in Lisp-like ones there are explicite forms for statement sequences (eg 'begin' form in Scheme). Also, numerous special forms are implicitely sequential (eg func def or cond in Lisp & Scheme), but not all (eg if). Thus, one can construct super-expressions in imperative-like style. But this is not considered good style, indeed.
 do {
      auto r = map!(sqr)(items);
 } where {
      int sqr(int x) pure nothrow {
          return x * x;
      }
 }
What's wrong with: { auto sqr = function int ( int ) { return x * x ; } ; auto r = map ! ( sqr ) ( items ) ; } Seems idiomatic and straightforward with less noise?
Right. But this does not make any distinction between logical sequence and construction sequence. A construction sequence can always, conceptually, be (re)composed into a single complex expression. Hope I'm clear ;-) Logically, the above indeed reads: "r is a mapping of sqr on items, where sqr is...". Compare with: this.update(); // effect on state return this.filter(pred);
 [...]
[OT]
 PS  I have begun to dislike languages that use semi-colon as a statement
 terminator!  (It took me 4 attempts to get my two line example to
 compile :-((
(Why 4? There are only 2 ';' there.) Lol! You're not the only one. 66% of my syntax errors are missing ';' (33% are using '=' instead of '==' in unittest asserts, even after years of python & Lua who inherited the same stupid operators). See also: http://www.dwheeler.com/readable/sweet-expressions.html for an example of trying to get rid of () noise in Lisp. Indented D would be a great language, visually: no more {} and ';'. Then, we could reuse {} for something else (eg set & aa literals, or template params). We would certainly find a nice use for ';'. Using '=' for equality and eg ':=' for assignment, D would then be close to perfect in terms of base syntax ;-). [/OT] Denis -- _________________ vita es estrany spir.wikidot.com
Mar 07 2011
prev sibling parent reply Caligo <iteronvexor gmail.com> writes:
I don't understand why so many here are obsessed with constantly trying to
"improve" D and/or find things that are wrong with the language just so they
can come up with a solution.  We've had feature freeze, have we not?

For someone who is relatively new to D, seeing all these discussions on
topics of improving or adding new things, I get the impression that the D
language is still in development and/or just an experimental language (like
Haskell).

I think digitalmars.D should be rename to digitalmars.D.R&D
Mar 07 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Caligo:

 I don't understand why so many here are obsessed with constantly trying to
 "improve" D and/or find things that are wrong with the language just so they
 can come up with a solution.  We've had feature freeze, have we not?
There are several unfinished features and parts in the current D2, so the right thing to do is to work on them and on bugs, instead of piling even more uncooked things on D2. On the other hand, a programming language is an alive thing, if it stops its development, it dies. People are designing even new C and Fortran features, and they are languages far older than D: http://en.wikipedia.org/wiki/C1X So new ideas for D3 language, or to fix D2 holes/warts are important. And language communities too are alive things. If there's no development of new ideas or fun things, the vitality of the community decreases. Bye, bearophile
Mar 08 2011
next sibling parent spir <denis.spir gmail.com> writes:
On 03/08/2011 09:37 AM, bearophile wrote:
 Caligo:

 I don't understand why so many here are obsessed with constantly trying to
 "improve" D and/or find things that are wrong with the language just so they
 can come up with a solution.  We've had feature freeze, have we not?
There are several unfinished features and parts in the current D2, so the right thing to do is to work on them and on bugs, instead of piling even more uncooked things on D2. On the other hand, a programming language is an alive thing, if it stops its development, it dies. People are designing even new C and Fortran features, and they are languages far older than D: http://en.wikipedia.org/wiki/C1X So new ideas for D3 language, or to fix D2 holes/warts are important. And language communities too are alive things. If there's no development of new ideas or fun things, the vitality of the community decreases.
Another point of view: D2 has been in great part designed _a priori_; this a priori design is a big part of what TDPL describes. As we all know (one of the few things history of programming has taught us), this is not often a sustainable way to develop, even for trivial apps. Instead, one discovers what a correct design should be along with guesses, trials, prototyping, implementation, testing, and real use, more real use, even more real use. Say, this is the creative and exploratory part of programming. A PL language is often a huge system, hyper complex, and very abstract (by definition, it is one level more abstract than any app program ;-). And D2 is a very big and complicated language; and far to be orthogonal, which means independently imaged features actually interact in unpredicted manners. (*) We should not allow D2, I guess, freeze on wrongly conceived (a priori) design; then, it's too late. No idea what Walter and Andrei think of this, but for me these are facts. You may think this is lost time, energy, & motivation; but the alternative is having an unusable PL; at least in its more innovative, interesting, and attractive features; which means all programmers would simply stick with Denis (*)Even one of its best thought features, problably: ranges, needs be constantly questionned; while this is precisely a feature that should be highly orthogonal to most other dimensions of the language, to be nearly transpatently usable --and used-- everywhere. We are still far to be there, I guess; ranges instead constantly get it the way (of my programming practice); to the point I know avoid them. Their greatest side is the ability to use algorithms uniformly with any collection, but the most commons of such algos (map, filter) are trivial funcs. -- _________________ vita es estrany spir.wikidot.com
Mar 08 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/8/11 12:37 AM, bearophile wrote:
 Caligo:

 I don't understand why so many here are obsessed with constantly trying to
 "improve" D and/or find things that are wrong with the language just so they
 can come up with a solution.  We've had feature freeze, have we not?
There are several unfinished features and parts in the current D2, so the right thing to do is to work on them and on bugs, instead of piling even more uncooked things on D2. On the other hand, a programming language is an alive thing, if it stops its development, it dies. People are designing even new C and Fortran features, and they are languages far older than D: http://en.wikipedia.org/wiki/C1X So new ideas for D3 language, or to fix D2 holes/warts are important. And language communities too are alive things. If there's no development of new ideas or fun things, the vitality of the community decreases. Bye, bearophile
In principle, of course. In practice, there are many nuances. Suggestions for improvements arising from systematic use of D in large programs are worth a lot of attention. On the other hand, taking a program written in idiomatic X, porting it to D, and then noticing that it doesn't look like the initial program and asking for D to acquire features from X is sometimes less productive. Andrei
Mar 08 2011