Re: [racket-dev] Code micro-level organization
[Combined reply] Two days ago, Jon Rafkind wrote: > On 05/30/2012 04:07 PM, Eli Barzilay wrote: > > > >> Having expressions come from the bottom, using the down arrow, seems > >> sort of wierd. > > Here's a concrete example: > > > > (○ (let ([x 10]) ↓) > > (for ([i (in-range x)]) ↓) > > (for ([j (in-range i)]) ↓) > > ...etc...) > > > > Do you have a concrete suggestion for doing that? > > Well in this case its easy, just remove the closing ) from each of > the lines so the next line is nested in the right place. Yes, but then you get the drift which is one of the things that it's trying to eliminate. (It's true that strictly speaking you don't need that -- but the premise of this whole thing is a tool for structuring code for easier reading & writing, like many other unnecessary tools (which are most of the language, of course).) > If you were going to use the down arrow in a different position, > like > > (for ([i ↓]) ..blah..) > (let ...) > > I think things would get out of hand quickly because the physical > gap between the for expression and the let expression could get > quite large. The use case for these things would be to simplify the code in a way that puts each "point of focus" on consecutive lines. To demonstrate with just the above, you *could* do something like this: ;; print a table of square roots (○ (for ([i ↓]) (printf "~s² = ~s\n" (* i i))) (in-range 0 100)) But that doesn't make much sense, since using the range expression inside the for loop is more readable. This is not different from this weird way to write this code: (let ([f (λ (x) (for ([i ↓]) (printf "~s² = ~s\n" (* i i]) (f (in-range 0 100))) Cases where you would use that is when that expression is that main focus of the code, for example (○ (for/list ([i ↓]) (* i i)) (in-sequences ↓ (in-range 10)) (stop-after ↓ zero?) (in-naturals ↓) (fib 10)) > I mean I hope I'm not trivializing your issue, do you have a > different example? Any example where that main focus is not the last expression, like the above. Here's a different one: (○ (for ([i 10]) ↓ (newline)) (for ([j 10]) ↓) (printf " ~s" (* i j))) Yesterday, Neil Toronto wrote: > > It seems like `↑' is another way to not name expressions (i.e. a > "pointless style" :D), (I can't parse that.) > and `↓' is handled just fine by internal definitions. This is > equivalent, currently defined, has less nesting, and avoids a > rename: > > (define orig-str "foo bar baz") > (define sub (substring orig-str 3 8)) > (define str (string-trim sub)) > (define m (regexp-match? #rx"^[a-z].*[a-z]$" str)) > (and m (string-append "*" str "*")) Yes, I mentioned this hack explicitly. Maybe it will help if I clarify why it's a hack: it forces you to choose names where you don't need any. For example, see how the names you chose are bad? -- `str' doesn't make sense in such a line, so it would become something like `trimmed-string', and `m' is a buggy convention since it's usually used with the results of matching so it should be something like `is-word?'. And now names are repeating information that is already clearly visible in the code, which makes me dislike it even more than the hugarian naming convention. Another way to deal with this is with a generic base for the name (which is what I remember Jay posting at some point), as in (define x1 ...) (define x2 ...) ... This is also a pretty bad way to do that, since the names are meaningless labels so the machine should be dealing with them, not me. (Note in particular how adding new intermediate expressions requires shifting names -- in a way that can eventually lead to a convention of `x10', `x20', etc so it's easier to debug...) But a more high-level point is that I *am* already using a highlevel language where names are not always necessary, and I'd like to keep that freedom without sacrificing code readability. The argument that is usually made for your example is "I find that being forced to use descriptive names help" -- that can obviously be true in some cases, but I dislike requiring it. A technical problem with this argument is that it omits the fact that you really need *unique* names; the high-level problem with the argument is that it's *exactly* the same argument that people have been using for ages as an excuse to not having some kind of `lambda' in their languages. > A `define*' form like Jay (and I) want would make these kinds of > things less error-prone and allow names to be reused: > [...] Right, and it looks like there's strong opposition to that, which is why I'm trying to find something that can be done. What I'm suggesting is not a complete replacement -- one thing it doesn't do is use different names for farther references (and that's why I asked for examples), and another difference is that I'm talking about a macro so you won't be able to use more than one `↑' (or whatever is a good name for
Re: [racket-dev] Code micro-level organization
I know about package-begin, it's just not worth it if I need to bring in another require and add package-begin Jay On Thu, May 31, 2012 at 3:43 PM, Ryan Culpepper wrote: > On 05/31/2012 02:54 PM, Jay McCarthy wrote: >> >> I was clapping through the majority of your email. >> >> I want define* so bad. > > > You can use define*; just put it inside of package-begin: > > > (require racket/package) > > (package-begin > (define* x 1) > (define* x (+ 2 x)) > x) > 3 > > I don't think I like the idea of making the internal definition contexts of > racket/base forms act like package-begin, though. > > Ryan > > >> I use compose and curry a lot (even though I know their performance >> problems) because it don't have to name things. >> >> I like the idea of the -> thing with the down and up arrows. I see a >> value in both arrows. I also like Jon's suggestion of a 'last' id... >> although I'd also want<0> through or something to refer to so >> many spots back. >> >> Jay >> >> On Wed, May 30, 2012 at 3:40 PM, Eli Barzilay wrote: >>> >>> I'm going to ramble a bit about organizing code, trying to look for an >>> idea for a good solution -- so spread a few kgs of salt over the >>> following (if you care to read it). >>> >>> The problem that I'm talking about has several manifestations. The >>> most obvious one is code-drift towards the RHS. A less obvious >>> problem is how it's sometimes hard to read code. To use a cooked up >>> example: >>> >>> (let ([str (string-trim (substring "foo bar baz" 3 8))]) >>> (and (regexp-match? #rx"^[a-z].*[a-z]$" str) >>> (string-append "*" str "*"))) >>> >>> to read this, you start from the string literal, then read the >>> `substring' expression, then `string-trim', then the `let' binding, >>> then the `and' and finally the `string-append'[*]. To relate this to the >>> above: besides the right-drift (which is of course very minor here), >>> it takes time to "internalize" the rules of the language that leads to >>> this, which is a problem for people new to functional programming with >>> it's heavy use of nested function calls. More than that, I think that >>> it's also a problem for *experienced* hackers too -- to see what I >>> mean, open up any random piece of code that deals with an area you're >>> not familiar with, and try to read through it. Personally, I often >>> find myself in such situations "reading" the actual ordering as I go >>> through the code, and that's fragile since I need to keep mental >>> fingers at various locations in the code in question, sometimes even >>> using my real fingers... >>> >>> You'd probably recognize that there's a whole bunch of tools that are >>> trying to make things better. A few random ones that I can think of >>> are: >>> >>> * The new semantics& blessing for using `define' forms instead of >>> >>> `let' etc makes code easier to read and avoids some right-drift. >>> >>> * There's the need (which I recently talked to at NEU) for some kind >>> of a `define*' form that can be used as a definition with a `let*' >>> scope. For those who weren't there, the summary of the issue is >>> something that Jay once said -- that he sometimes uses >>> (define x0 ...) >>> (define x1 (... x0 ...)) >>> (define x2 (... x1 ...)) >>> because he wants to avoid a `let*'. >>> >>> * The old `scheme/nest' is a direct attempt to prevent drift for >>> some kinds of nestings. >>> >>> * There's the related suggestion for extending the reader with >>> something like `$' or `//' that closes the rest of the sexpr in >>> its own set of parens. >>> >>> * Every once in a while there's a suggestion to invert conversion >>> functions, eg, turn `string->number' into `number<-string' so it >>> reads out better. In a similar direction, there are sometimes >>> suggestions to use `compose' to make things more readable, as in >>> ((compose f1 f2 f3 f4) x) >>> vs >>> (f1 (f2 (f3 (f4 x >>> and the textual mess that the latter tends to end up as with real >>> names. >>> >>> * srfi-2 defines an `and-let*' which is addressing a common pattern >>> of interleaving nested `let's and `and's. Actually, `cond' itself >>> is addressing this kind of problem too, so add here various >>> suggestions for extending `cond' with binders, anaphoric forms >>> etc. >>> >>> * Recently, I looked at some clojure pages (to hunt for new >>> extensions to `racket/list'), and I saw that they have a >>> "threading form" using `->' that expresses nested function calls. >>> See this here: >>> http://clojuredocs.org/clojure_core/clojure.core/-%3E >>> and note also the other three variants, `->>' `-?>' and `-?>>', >>> >>> * (The list goes on...) >>> >>> (One common theme in all of these is that they're tools that none of >>> them are tools that are needed -- they're all just ways to make code >>> look better.) >>> >>> I actually started thinking about this when I saw the
Re: [racket-dev] Code micro-level organization
On 05/31/2012 02:54 PM, Jay McCarthy wrote: I was clapping through the majority of your email. I want define* so bad. You can use define*; just put it inside of package-begin: > (require racket/package) > (package-begin (define* x 1) (define* x (+ 2 x)) x) 3 I don't think I like the idea of making the internal definition contexts of racket/base forms act like package-begin, though. Ryan I use compose and curry a lot (even though I know their performance problems) because it don't have to name things. I like the idea of the -> thing with the down and up arrows. I see a value in both arrows. I also like Jon's suggestion of a 'last' id... although I'd also want<0> through or something to refer to so many spots back. Jay On Wed, May 30, 2012 at 3:40 PM, Eli Barzilay wrote: I'm going to ramble a bit about organizing code, trying to look for an idea for a good solution -- so spread a few kgs of salt over the following (if you care to read it). The problem that I'm talking about has several manifestations. The most obvious one is code-drift towards the RHS. A less obvious problem is how it's sometimes hard to read code. To use a cooked up example: (let ([str (string-trim (substring "foo bar baz" 3 8))]) (and (regexp-match? #rx"^[a-z].*[a-z]$" str) (string-append "*" str "*"))) to read this, you start from the string literal, then read the `substring' expression, then `string-trim', then the `let' binding, then the `and' and finally the `string-append'[*]. To relate this to the above: besides the right-drift (which is of course very minor here), it takes time to "internalize" the rules of the language that leads to this, which is a problem for people new to functional programming with it's heavy use of nested function calls. More than that, I think that it's also a problem for *experienced* hackers too -- to see what I mean, open up any random piece of code that deals with an area you're not familiar with, and try to read through it. Personally, I often find myself in such situations "reading" the actual ordering as I go through the code, and that's fragile since I need to keep mental fingers at various locations in the code in question, sometimes even using my real fingers... You'd probably recognize that there's a whole bunch of tools that are trying to make things better. A few random ones that I can think of are: * The new semantics& blessing for using `define' forms instead of `let' etc makes code easier to read and avoids some right-drift. * There's the need (which I recently talked to at NEU) for some kind of a `define*' form that can be used as a definition with a `let*' scope. For those who weren't there, the summary of the issue is something that Jay once said -- that he sometimes uses (define x0 ...) (define x1 (... x0 ...)) (define x2 (... x1 ...)) because he wants to avoid a `let*'. * The old `scheme/nest' is a direct attempt to prevent drift for some kinds of nestings. * There's the related suggestion for extending the reader with something like `$' or `//' that closes the rest of the sexpr in its own set of parens. * Every once in a while there's a suggestion to invert conversion functions, eg, turn `string->number' into `number<-string' so it reads out better. In a similar direction, there are sometimes suggestions to use `compose' to make things more readable, as in ((compose f1 f2 f3 f4) x) vs (f1 (f2 (f3 (f4 x and the textual mess that the latter tends to end up as with real names. * srfi-2 defines an `and-let*' which is addressing a common pattern of interleaving nested `let's and `and's. Actually, `cond' itself is addressing this kind of problem too, so add here various suggestions for extending `cond' with binders, anaphoric forms etc. * Recently, I looked at some clojure pages (to hunt for new extensions to `racket/list'), and I saw that they have a "threading form" using `->' that expresses nested function calls. See this here: http://clojuredocs.org/clojure_core/clojure.core/-%3E and note also the other three variants, `->>' `-?>' and `-?>>', * (The list goes on...) (One common theme in all of these is that they're tools that none of them are tools that are needed -- they're all just ways to make code look better.) I actually started thinking about this when I saw the clojure thing. The first thing that is limited about it is that it has four forms, where the reason for the `->' vs `->>' split is to put the nesting in a different argument position. To summarize (and IIUC): (-> x (foo 1 2) (bar y)) expands to (bar (foo x 1 2) y) whereas using a `->>' would make it expand to (bar y (foo 1 2 x)) Not only does it seem to me bad to have two bindings for this, we also have the usual problem of the order-defying `regexp-replace' where usually the ac
Re: [racket-dev] Code micro-level organization
I was clapping through the majority of your email. I want define* so bad. I use compose and curry a lot (even though I know their performance problems) because it don't have to name things. I like the idea of the -> thing with the down and up arrows. I see a value in both arrows. I also like Jon's suggestion of a 'last' id... although I'd also want <0> through or something to refer to so many spots back. Jay On Wed, May 30, 2012 at 3:40 PM, Eli Barzilay wrote: > I'm going to ramble a bit about organizing code, trying to look for an > idea for a good solution -- so spread a few kgs of salt over the > following (if you care to read it). > > The problem that I'm talking about has several manifestations. The > most obvious one is code-drift towards the RHS. A less obvious > problem is how it's sometimes hard to read code. To use a cooked up > example: > > (let ([str (string-trim (substring "foo bar baz" 3 8))]) > (and (regexp-match? #rx"^[a-z].*[a-z]$" str) > (string-append "*" str "*"))) > > to read this, you start from the string literal, then read the > `substring' expression, then `string-trim', then the `let' binding, > then the `and' and finally the `string-append'[*]. To relate this to the > above: besides the right-drift (which is of course very minor here), > it takes time to "internalize" the rules of the language that leads to > this, which is a problem for people new to functional programming with > it's heavy use of nested function calls. More than that, I think that > it's also a problem for *experienced* hackers too -- to see what I > mean, open up any random piece of code that deals with an area you're > not familiar with, and try to read through it. Personally, I often > find myself in such situations "reading" the actual ordering as I go > through the code, and that's fragile since I need to keep mental > fingers at various locations in the code in question, sometimes even > using my real fingers... > > You'd probably recognize that there's a whole bunch of tools that are > trying to make things better. A few random ones that I can think of > are: > > * The new semantics & blessing for using `define' forms instead of > `let' etc makes code easier to read and avoids some right-drift. > > * There's the need (which I recently talked to at NEU) for some kind > of a `define*' form that can be used as a definition with a `let*' > scope. For those who weren't there, the summary of the issue is > something that Jay once said -- that he sometimes uses > (define x0 ...) > (define x1 (... x0 ...)) > (define x2 (... x1 ...)) > because he wants to avoid a `let*'. > > * The old `scheme/nest' is a direct attempt to prevent drift for > some kinds of nestings. > > * There's the related suggestion for extending the reader with > something like `$' or `//' that closes the rest of the sexpr in > its own set of parens. > > * Every once in a while there's a suggestion to invert conversion > functions, eg, turn `string->number' into `number<-string' so it > reads out better. In a similar direction, there are sometimes > suggestions to use `compose' to make things more readable, as in > ((compose f1 f2 f3 f4) x) > vs > (f1 (f2 (f3 (f4 x > and the textual mess that the latter tends to end up as with real > names. > > * srfi-2 defines an `and-let*' which is addressing a common pattern > of interleaving nested `let's and `and's. Actually, `cond' itself > is addressing this kind of problem too, so add here various > suggestions for extending `cond' with binders, anaphoric forms > etc. > > * Recently, I looked at some clojure pages (to hunt for new > extensions to `racket/list'), and I saw that they have a > "threading form" using `->' that expresses nested function calls. > See this here: > http://clojuredocs.org/clojure_core/clojure.core/-%3E > and note also the other three variants, `->>' `-?>' and `-?>>', > > * (The list goes on...) > > (One common theme in all of these is that they're tools that none of > them are tools that are needed -- they're all just ways to make code > look better.) > > I actually started thinking about this when I saw the clojure thing. > The first thing that is limited about it is that it has four forms, > where the reason for the `->' vs `->>' split is to put the nesting in > a different argument position. To summarize (and IIUC): > > (-> x > (foo 1 2) > (bar y)) > > expands to > > (bar (foo x 1 2) y) > > whereas using a `->>' would make it expand to > > (bar y (foo 1 2 x)) > > Not only does it seem to me bad to have two bindings for this, we also > have the usual problem of the order-defying `regexp-replace' where > usually the action happens in the *middle* argument... (Which is how > it ends up being a common example in showing these problems, as > happened recently.) > > In any case, this looks like an easy thing to fix by adding an > ex
Re: [racket-dev] Code micro-level organization
(sorry for the bad indentation, writing s-exps on a phone predictive keyboard is painful...) Le 31 mai 2012 19:21, "Laurent" a écrit : > How about a define* that is exactly like let* without the additional > indentation level? > E.g.: > > (define* > [↑ "foo bar baz"] > [↑ (substring ↑ 3 8)] > [str (string-trim ↑)] > [↑ (regexp-match? #rx"^[a-z].*[a-z]$" str)]) > (and ↑ (string-append "*" str "*")) > > Laurent > > Le 31 mai 2012 19:04, "Neil Toronto" a écrit : > > > > On 05/30/2012 03:40 PM, Eli Barzilay wrote: > >> > >> Now, lets imagine that instead of a simple `<>' hole, there are two > >> kinds of holes with an "up" or a "down" direction -- this leads to > >> this kind of a syntax: > >> > >> (○ "foo bar baz" > >> (substring ↑ 3 8) > >> (string-trim ↑) > >> (let ([str ↑]) ↓) > >> (and (regexp-match? #rx"^[a-z].*[a-z]$" str) ↓) > >> (string-append "*" str "*")) > >> > >> where you can read `↑' as "the above" and `↓' as "the below". > > > > > > It seems like `↑' is another way to not name expressions (i.e. a > "pointless style" :D), and `↓' is handled just fine by internal > definitions. This is equivalent, currently defined, has less nesting, and > avoids a rename: > > > > (define orig-str "foo bar baz") > > (define sub (substring orig-str 3 8)) > > (define str (string-trim sub)) > > (define m (regexp-match? #rx"^[a-z].*[a-z]$" str)) > > (and m (string-append "*" str "*")) > > > > A `define*' form like Jay (and I) want would make these kinds of things > less error-prone and allow names to be reused: > > > > (define* ↑ "foo bar baz") > > (define* ↑ (substring ↑ 3 8)) > > (define* str (string-trim ↑)) > > (define* ↑ (regexp-match? #rx"^[a-z].*[a-z]$" str)) > > (and ↑ (string-append "*" str "*")) > > > > It's still pretty wordy, though. Even if I had `define*' I'd be tempted > to go with my current favorite idiom, which trades wordiness for a nesting > level: > > > > (let* ([↑"foo bar baz"] > > [↑(substring ↑ 3 8)] > > [str (string-trim ↑)] > > [↑(regexp-match? #rx"^[a-z].*[a-z]$" str)]) > > (and ↑ (string-append "*" str "*"))) > > > > I occasionally get annoyed by how deeply these can get nested. I feel > your pain, man. > > > > Neil ⊥ > > > > _ > > Racket Developers list: > > http://lists.racket-lang.org/dev > _ Racket Developers list: http://lists.racket-lang.org/dev
Re: [racket-dev] Code micro-level organization
How about a define* that is exactly like let* without the additional indentation level? E.g.: (define* [↑ "foo bar baz"] [↑ (substring ↑ 3 8)] [str (string-trim ↑)] [↑ (regexp-match? #rx"^[a-z].*[a-z]$" str)]) (and ↑ (string-append "*" str "*")) Laurent Le 31 mai 2012 19:04, "Neil Toronto" a écrit : > > On 05/30/2012 03:40 PM, Eli Barzilay wrote: >> >> Now, lets imagine that instead of a simple `<>' hole, there are two >> kinds of holes with an "up" or a "down" direction -- this leads to >> this kind of a syntax: >> >> (○ "foo bar baz" >> (substring ↑ 3 8) >> (string-trim ↑) >> (let ([str ↑]) ↓) >> (and (regexp-match? #rx"^[a-z].*[a-z]$" str) ↓) >> (string-append "*" str "*")) >> >> where you can read `↑' as "the above" and `↓' as "the below". > > > It seems like `↑' is another way to not name expressions (i.e. a "pointless style" :D), and `↓' is handled just fine by internal definitions. This is equivalent, currently defined, has less nesting, and avoids a rename: > > (define orig-str "foo bar baz") > (define sub (substring orig-str 3 8)) > (define str (string-trim sub)) > (define m (regexp-match? #rx"^[a-z].*[a-z]$" str)) > (and m (string-append "*" str "*")) > > A `define*' form like Jay (and I) want would make these kinds of things less error-prone and allow names to be reused: > > (define* ↑ "foo bar baz") > (define* ↑ (substring ↑ 3 8)) > (define* str (string-trim ↑)) > (define* ↑ (regexp-match? #rx"^[a-z].*[a-z]$" str)) > (and ↑ (string-append "*" str "*")) > > It's still pretty wordy, though. Even if I had `define*' I'd be tempted to go with my current favorite idiom, which trades wordiness for a nesting level: > > (let* ([↑"foo bar baz"] > [↑(substring ↑ 3 8)] > [str (string-trim ↑)] > [↑(regexp-match? #rx"^[a-z].*[a-z]$" str)]) > (and ↑ (string-append "*" str "*"))) > > I occasionally get annoyed by how deeply these can get nested. I feel your pain, man. > > Neil ⊥ > > _ > Racket Developers list: > http://lists.racket-lang.org/dev _ Racket Developers list: http://lists.racket-lang.org/dev
Re: [racket-dev] Code micro-level organization
On 05/30/2012 03:40 PM, Eli Barzilay wrote: Now, lets imagine that instead of a simple `<>' hole, there are two kinds of holes with an "up" or a "down" direction -- this leads to this kind of a syntax: (○ "foo bar baz" (substring ↑ 3 8) (string-trim ↑) (let ([str ↑]) ↓) (and (regexp-match? #rx"^[a-z].*[a-z]$" str) ↓) (string-append "*" str "*")) where you can read `↑' as "the above" and `↓' as "the below". It seems like `↑' is another way to not name expressions (i.e. a "pointless style" :D), and `↓' is handled just fine by internal definitions. This is equivalent, currently defined, has less nesting, and avoids a rename: (define orig-str "foo bar baz") (define sub (substring orig-str 3 8)) (define str (string-trim sub)) (define m (regexp-match? #rx"^[a-z].*[a-z]$" str)) (and m (string-append "*" str "*")) A `define*' form like Jay (and I) want would make these kinds of things less error-prone and allow names to be reused: (define* ↑ "foo bar baz") (define* ↑ (substring ↑ 3 8)) (define* str (string-trim ↑)) (define* ↑ (regexp-match? #rx"^[a-z].*[a-z]$" str)) (and ↑ (string-append "*" str "*")) It's still pretty wordy, though. Even if I had `define*' I'd be tempted to go with my current favorite idiom, which trades wordiness for a nesting level: (let* ([↑"foo bar baz"] [↑(substring ↑ 3 8)] [str (string-trim ↑)] [↑(regexp-match? #rx"^[a-z].*[a-z]$" str)]) (and ↑ (string-append "*" str "*"))) I occasionally get annoyed by how deeply these can get nested. I feel your pain, man. Neil ⊥ _ Racket Developers list: http://lists.racket-lang.org/dev
Re: [racket-dev] Code micro-level organization
On 05/30/2012 04:07 PM, Eli Barzilay wrote: > >> Having expressions come from the bottom, using the down arrow, seems >> sort of wierd. > Here's a concrete example: > > (○ (let ([x 10]) ↓) > (for ([i (in-range x)]) ↓) > (for ([j (in-range i)]) ↓) > ...etc...) > > Do you have a concrete suggestion for doing that? > Well in this case its easy, just remove the closing ) from each of the lines so the next line is nested in the right place. If you were going to use the down arrow in a different position, like (for ([i ↓]) ..blah..) (let ...) I think things would get out of hand quickly because the physical gap between the for expression and the let expression could get quite large. I mean I hope I'm not trivializing your issue, do you have a different example? _ Racket Developers list: http://lists.racket-lang.org/dev
Re: [racket-dev] Code micro-level organization
A few minutes ago, Jon Rafkind wrote: > On 05/30/2012 03:40 PM, Eli Barzilay wrote: > > Now, lets imagine that instead of a simple `<>' hole, there are two > > kinds of holes with an "up" or a "down" direction -- this leads to > > this kind of a syntax: > > > > (○ "foo bar baz" > > (substring ↑ 3 8) > > (string-trim ↑) > > (let ([str ↑]) ↓) > > (and (regexp-match? #rx"^[a-z].*[a-z]$" str) ↓) > > (string-append "*" str "*")) > > > > where you can read `↑' as "the above" and `↓' as "the below". The > > thing that makes me excited about this is how you can read this as the > > above [*] reading. > > Maybe a simpler proposal is just a 'last' identifier that is always > bound to the previous expression? That's basically the (improved) thing that you get in clojure, and the problem is that it doesn't fit nesting in the other way, which is what `nest' does. Something that I forgot to fill in the previous post is that this could be one reason why they have the `-?>' and `-?>>' forms: these things are dealing with conditional values, which is something that you get with nesting that goes in the other direction. (As for the identifier names, I think of them as "the above" and "the below", so maybe those are better names.) > Having expressions come from the bottom, using the down arrow, seems > sort of wierd. Here's a concrete example: (○ (let ([x 10]) ↓) (for ([i (in-range x)]) ↓) (for ([j (in-range i)]) ↓) ...etc...) Do you have a concrete suggestion for doing that? -- ((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay: http://barzilay.org/ Maze is Life! _ Racket Developers list: http://lists.racket-lang.org/dev
Re: [racket-dev] Code micro-level organization
On 05/30/2012 03:40 PM, Eli Barzilay wrote: > Now, lets imagine that instead of a simple `<>' hole, there are two > kinds of holes with an "up" or a "down" direction -- this leads to > this kind of a syntax: > > (○ "foo bar baz" > (substring ↑ 3 8) > (string-trim ↑) > (let ([str ↑]) ↓) > (and (regexp-match? #rx"^[a-z].*[a-z]$" str) ↓) > (string-append "*" str "*")) > > where you can read `↑' as "the above" and `↓' as "the below". The > thing that makes me excited about this is how you can read this as the > above [*] reading. Maybe a simpler proposal is just a 'last' identifier that is always bound to the previous expression? I think having two arrows could get confusing. I'm not really a fan of the `let' expression binding `str' in the scope of all the expressions below it since they only occur in the `let' due to the down arrow. But with "last" it would be: (* "foo bar baz" (substring last 3 8) (string-trim last) (let ([str last]) (* (string-append "*" str "*")) (and (regexp-match ... str) last))) Having expressions come from the bottom, using the down arrow, seems sort of wierd. _ Racket Developers list: http://lists.racket-lang.org/dev
[racket-dev] Code micro-level organization
I'm going to ramble a bit about organizing code, trying to look for an idea for a good solution -- so spread a few kgs of salt over the following (if you care to read it). The problem that I'm talking about has several manifestations. The most obvious one is code-drift towards the RHS. A less obvious problem is how it's sometimes hard to read code. To use a cooked up example: (let ([str (string-trim (substring "foo bar baz" 3 8))]) (and (regexp-match? #rx"^[a-z].*[a-z]$" str) (string-append "*" str "*"))) to read this, you start from the string literal, then read the `substring' expression, then `string-trim', then the `let' binding, then the `and' and finally the `string-append'[*]. To relate this to the above: besides the right-drift (which is of course very minor here), it takes time to "internalize" the rules of the language that leads to this, which is a problem for people new to functional programming with it's heavy use of nested function calls. More than that, I think that it's also a problem for *experienced* hackers too -- to see what I mean, open up any random piece of code that deals with an area you're not familiar with, and try to read through it. Personally, I often find myself in such situations "reading" the actual ordering as I go through the code, and that's fragile since I need to keep mental fingers at various locations in the code in question, sometimes even using my real fingers... You'd probably recognize that there's a whole bunch of tools that are trying to make things better. A few random ones that I can think of are: * The new semantics & blessing for using `define' forms instead of `let' etc makes code easier to read and avoids some right-drift. * There's the need (which I recently talked to at NEU) for some kind of a `define*' form that can be used as a definition with a `let*' scope. For those who weren't there, the summary of the issue is something that Jay once said -- that he sometimes uses (define x0 ...) (define x1 (... x0 ...)) (define x2 (... x1 ...)) because he wants to avoid a `let*'. * The old `scheme/nest' is a direct attempt to prevent drift for some kinds of nestings. * There's the related suggestion for extending the reader with something like `$' or `//' that closes the rest of the sexpr in its own set of parens. * Every once in a while there's a suggestion to invert conversion functions, eg, turn `string->number' into `number<-string' so it reads out better. In a similar direction, there are sometimes suggestions to use `compose' to make things more readable, as in ((compose f1 f2 f3 f4) x) vs (f1 (f2 (f3 (f4 x and the textual mess that the latter tends to end up as with real names. * srfi-2 defines an `and-let*' which is addressing a common pattern of interleaving nested `let's and `and's. Actually, `cond' itself is addressing this kind of problem too, so add here various suggestions for extending `cond' with binders, anaphoric forms etc. * Recently, I looked at some clojure pages (to hunt for new extensions to `racket/list'), and I saw that they have a "threading form" using `->' that expresses nested function calls. See this here: http://clojuredocs.org/clojure_core/clojure.core/-%3E and note also the other three variants, `->>' `-?>' and `-?>>', * (The list goes on...) (One common theme in all of these is that they're tools that none of them are tools that are needed -- they're all just ways to make code look better.) I actually started thinking about this when I saw the clojure thing. The first thing that is limited about it is that it has four forms, where the reason for the `->' vs `->>' split is to put the nesting in a different argument position. To summarize (and IIUC): (-> x (foo 1 2) (bar y)) expands to (bar (foo x 1 2) y) whereas using a `->>' would make it expand to (bar y (foo 1 2 x)) Not only does it seem to me bad to have two bindings for this, we also have the usual problem of the order-defying `regexp-replace' where usually the action happens in the *middle* argument... (Which is how it ends up being a common example in showing these problems, as happened recently.) In any case, this looks like an easy thing to fix by adding an explicit marker to the point where the nesting happens. For example, imagine a form that looks like this: (○ x (foo 1 <> 2) (bar y <>)) that expands to (bar y (foo 1 x 2)). (The reason that clojure has two other forms (`-?>' and `-?>>') is something that is related to the below, so I'll skip it for now.) The next thing that I tried is to contrast this with `nest'. The difference between them is that while both lead to a simpler syntax for nested expressions, they do the nesting in different directions, where (*very* roughly speaking) `->' nests things downwards and `nest' nests them upwards: