Cheers all I have noticed that I often ends up with quite a number of nested expressions, for example: (((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ] What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax? My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ] Basically, a send the right hand expression to the result of the left hand expression. Has anyone ever tried this, or is it just one of the many small annoyances best left alone? Best, Kasper
On Wed, Jan 26, 2022, 01:20 Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
What's written is basically unreadable and syntax changes won't help. Try taking out the constants and describing their relationship with a new message or a couple of messages. Without trying to understand it, it looks like you have some navigation followed by some filtering / suffix selection. Try to develop an expression that articulates the meaning of what you're doing.
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
I agree that the expression is unreadable. What I would initially do is to extract it into temporary variables. Like: tree := json at: 'tree'. blobs := tree select: [ :each | (each at: 'type') = 'blob' ]. paths := blobs collect: [ :each | Path from: (each at: 'path')]). ... After you can see if you can extract the logic into different classes or other methods. Sebastian
De: "Richard Sargent" <richard.sargent@gemtalksystems.com> Ã: "pharo-users" <pharo-users@lists.pharo.org> Envoyé: Mercredi 26 Janvier 2022 09:55:30 Objet: [Pharo-users] Re: Too many parenthesis - a matter of syntax
On Wed, Jan 26, 2022, 01:20 Kasper Osterbye < [ mailto:kasper.osterbye@gmail.com | kasper.osterbye@gmail.com ] > wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
What's written is basically unreadable and syntax changes won't help.
Try taking out the constants and describing their relationship with a new message or a couple of messages.
Without trying to understand it, it looks like you have some navigation followed by some filtering / suffix selection.
Try to develop an expression that articulates the meaning of what you're doing.
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
But does this help? Now the simple nested query is some serious code. I, too, like the idea that a query expression like the one shown should have a nice syntax⦠without the need for variables. I always thought that the nice collection API would be nicer if we could write them without parenthesis... How to do that right is of course not easy. There are experiments around, for example https://github.com/dvmason/Pharo-Functional
On 26 Jan 2022, at 11:11, Sebastian Jordan Montano <sebastian.jordan@inria.fr> wrote:
I agree that the expression is unreadable. What I would initially do is to extract it into temporary variables. Like:
tree := json at: 'tree'. blobs := tree select: [ :each | (each at: 'type') = 'blob' ]. paths := blobs collect: [ :each | Path from: (each at: 'path')]). ...
After you can see if you can extract the logic into different classes or other methods.
Sebastian
De: "Richard Sargent" <richard.sargent@gemtalksystems.com> Ã: "pharo-users" <pharo-users@lists.pharo.org> Envoyé: Mercredi 26 Janvier 2022 09:55:30 Objet: [Pharo-users] Re: Too many parenthesis - a matter of syntax On Wed, Jan 26, 2022, 01:20 Kasper Osterbye <kasper.osterbye@gmail.com <mailto:kasper.osterbye@gmail.com>> wrote: Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
What's written is basically unreadable and syntax changes won't help.
Try taking out the constants and describing their relationship with a new message or a couple of messages.
Without trying to understand it, it looks like you have some navigation followed by some filtering / suffix selection.
Try to develop an expression that articulates the meaning of what you're doing.
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
Marcus Denker wrote:
There are experiments around, for example
And https://github.com/juliendelplanque/Iterators, which letâs you do for example: ``` iterator := #(1 2 3) iterator. iterator | [ :x | x * 2 ] collectIt | [ :object | object logCr ] doIt "Just print incoming objects in transcript." > NullAddableObject "Special object that ignore incoming objects." ```
Kasper, I wouldn't say your suggestion looks any more readable than the many parentheses of the original expression. The whole operation is complex enough to be hard to understand. So I agree with both Richard and Sebastian - you should either make the steps readable by giving the intermediate results good names or factor the whole operation out into some object and good method name (which would be hard to read in itself without the temp vars, so you should pobably combine both suggestions). My opinion may be irrelevant, as I don't hold a "real" CS degree. I look at this from the perspective of the poor guy who has to debug this in three years rather than from an angle of elegance or conciseness. I guess even something like the .then() - syntax from Javascrpt wouldn't make this any more readable. And whatever symbol you'd chose it wouldn't communicate what's happening... Joachim Am 26.01.22 um 10:19 schrieb Kasper Osterbye:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
-- ----------------------------------------------------------------------- Objektfabrik Joachim Tuchel mailto:jtuchel@objektfabrik.de Fliederweg 1 http://www.objektfabrik.de D-71640 Ludwigsburg http://joachimtuchel.wordpress.com Telefon: +49 7141 56 10 86 0 Fax: +49 7141 56 10 86 1
Hi Kaspar, I found the initial example actually reasonable readable. However, I would simplify it as follows: (json at: #tree) select: [ :each | ((each at: #type) = #blob) and: [ #(md mic) includes: (Path from: (each at: #path)) extension ] ]. I would personally not try to extend the Smalltalk syntax. The cost is not worth the gain. Tools could be written to help in dealing with deeply nested structures (JSON/XML), they could form their own DSL at a higher level. For a limited example, NeoJSONObject uses the DNU trick to support unary accessors: json at: #tree can be written as json tree and has a path accessor: json atPath: #(tree field name) it also behaves like JavaScript objects in that missing keys are nil. It is not that all this is good or better, it is just handy in some cases. Your code would then be even simpler (less parenthesis): json tree select: [ :each | (each type = #blob) and: [ #(md mic) includes: (Path from: each path) extension ] ]. Sven
On 26 Jan 2022, at 10:19, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
Also...learning to think in Smalltalk has taught me to understand Lisp. As an Emacs guy, that is a good thing (:
On Wed, Jan 26, 2022 at 4:40 AM Sven Van Caekenberghe <sven@stfx.eu> wrote:
Hi Kaspar,
I found the initial example actually reasonable readable.
However, I would simplify it as follows:
(json at: #tree) select: [ :each | ((each at: #type) = #blob) and: [ #(md mic) includes: (Path from: (each at: #path)) extension ] ].
I would personally not try to extend the Smalltalk syntax. The cost is not worth the gain.
Tools could be written to help in dealing with deeply nested structures (JSON/XML), they could form their own DSL at a higher level.
For a limited example, NeoJSONObject uses the DNU trick to support unary accessors:
json at: #tree
can be written as
json tree
and has a path accessor:
json atPath: #(tree field name)
it also behaves like JavaScript objects in that missing keys are nil. It is not that all this is good or better, it is just handy in some cases. Your code would then be even simpler (less parenthesis):
json tree select: [ :each | (each type = #blob) and: [ #(md mic) includes: (Path from: each path) extension ] ].
That is the kind of expressiveness I was looking for!
Sven
On 26 Jan 2022, at 10:19, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
WAIPSessionTrackingStrategy > isAbstract   ^true has just ran into my new hobby horse. I am reading through every class in Seaside to document it and I had an 'aha!' moment on the design of Abstract super classes like WABrush: WABrush > isAbstract       ^ self == WABrush The simplicity is genius because no subclass of WABrush has to implement isAbstract, whereas in WAIPSessionTrackingStrategy, concrete subclasses (of which there currently are none) will have to. In defense of the WAIPSessionTrackingStrategy implementation, the comment reads: I track sessions using the remote address of a client. Usually you don't want to use this because it causes troubles when multiple users share the same IP (eg. several users from the same company). That's why I'm marked as abstract. There are some special cases like crawlers where it can work though.  So, apparently, the author is anticipating somebody someday using it.
Y
On 26 Jan 2022, at 15:55, gettimothy via Pharo-users <pharo-users@lists.pharo.org> wrote:
WAIPSessionTrackingStrategy > isAbstract ^true has just ran into my new hobby horse.
I am reading through every class in Seaside to document it and I had an 'aha!' moment
on the design of Abstract super classes like WABrush:
WABrush > isAbstract
^ self == WABrush
The simplicity is genius because no subclass of WABrush has to implement isAbstract, whereas in WAIPSessionTrackingStrategy, concrete subclasses (of which there currently are none) will have to.
Exactly, that is the idea of the pattern. On the downside it is not easy to understand (you have to think about it the first time you see it) and it feels (to me) âuglyâ. Maybe because it encodes meta-data of the class, which should be declarative, as a method? Instead of improving the meta-model (and thus our understanding) of what a class is? But it is the best we have now. There is even a âmake class abstractâ refactoring method in Pharo10, and we adapted the Code Critique to not complain about the direct class reference for these methods.
In defense of the WAIPSessionTrackingStrategy implementation, the comment reads: I track sessions using the remote address of a client. Usually you don't want to use this because it causes troubles when multiple users share the same IP (eg. several users from the same company).
That's why I'm marked as abstract.
There are some special cases like crawlers where it can work though.
So, apparently, the author is anticipating somebody someday using it.
Maybe you can add an issue to the seaside issue tracker? Marcus
I understand the point of not adding more syntax and I agree with it, but I think this is such big missing feature in Pharo and that it could be provided by default in the image as an API instead of a syntax sugar. https://github.com/dvmason/Pharo-Functional has both approaches â syntax and API â and I really like the `chain` strategy â I had implemented one of my own some years ago based on a suggestion made here on this list. So the original example code would be like this: json *chain* at: âtree'; select: [ :e | ((e at: 'type') = 'blobâ)]; collect: [:e | Path from: (e at: 'pathâ)]; select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ] The chain stores the last returned object to be used as the receiver of the next message send. You can do it using a DNU, but maybe there is a more optimized way of doing it, and also using DNU do not work with messages present on the chain base class (Object), maybe tehre is a way to circumvent that also. Regards, Vitor On Wed, Jan 26, 2022 at 11:33 AM Richard Sargent < richard.sargent@gemtalksystems.com> wrote:
On Wed, Jan 26, 2022 at 4:40 AM Sven Van Caekenberghe <sven@stfx.eu> wrote:
Hi Kaspar,
I found the initial example actually reasonable readable.
However, I would simplify it as follows:
(json at: #tree) select: [ :each | ((each at: #type) = #blob) and: [ #(md mic) includes: (Path from: (each at: #path)) extension ] ].
I would personally not try to extend the Smalltalk syntax. The cost is not worth the gain.
Tools could be written to help in dealing with deeply nested structures (JSON/XML), they could form their own DSL at a higher level.
For a limited example, NeoJSONObject uses the DNU trick to support unary accessors:
json at: #tree
can be written as
json tree
and has a path accessor:
json atPath: #(tree field name)
it also behaves like JavaScript objects in that missing keys are nil. It is not that all this is good or better, it is just handy in some cases. Your code would then be even simpler (less parenthesis):
json tree select: [ :each | (each type = #blob) and: [ #(md mic) includes: (Path from: each path) extension ] ].
That is the kind of expressiveness I was looking for!
Sven
On 26 Jan 2022, at 10:19, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
I like the parenthesis. The thinking process is very much like "thinking in Sets" when building a complex SQL statement. The result is a lot of power in an dense, elegant expression. If, as is often the case with me, I find a nested expression as below too complex, it is very easy to decompose it. I realize this is just a preference, but the denseness forces me to think in Objects and composition rather than process. just my .02 cents. cheers. ---- On Wed, 26 Jan 2022 04:19:39 -0500 Kasper Osterbye <kasper.osterbye@gmail.com> wrote ---- Cheers all I have noticed that I often ends up with quite a number of nested expressions, for example: (((json at: 'tree')     select: [ :e | (e at: 'type') = âblob' ])         collect: [:e | Path from: (e at: 'path')])             select: [ :p | p segments last                 in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ] What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax? My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º     select: [ :e | ((e at: 'type') = 'blobâ)]º     collect: [:e | Path from: (e at: 'pathâ)]º     select: [ :p | p segments last         in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ] Basically, a send the right hand expression to the result of the left hand expression. Has anyone ever tried this, or is it just one of the many small annoyances best left alone? Best, Kasper
On Wed, Jan 26, 2022 at 10:58 AM gettimothy via Pharo-users <pharo-users@lists.pharo.org> wrote:
I realize this is just a preference, but the denseness forces me to think in Objects and composition rather than process.
I never thought of it this way, but I fully agree with it. Thanks for putting this in words. Maybe what we need is better highlighting of the current "scope" where the cursor is. Navigating in and out of the parenthesis is not trivial, and it feels like hunting sometimes. With Futures it would be convenient to be able to link different #then: and such, but what you might need is a "chained" object that does all that for you. I always think of ways to improve the current syntax, but in the end it seems like anything else feels unnatural, and the net result is not positive. So... more objects, less syntax... and better tooling. Regards, Esteban A. Maringolo
What I like about parentheses (and I hate parentheses) is that it allows me to view/describe the intent of the code. When parens are not used, it is up to me (perhaps years later) to determine whether the original developer (sometimes me) understood (or not) the actual 'order of processing'. Parens - or another vehicle - should help me understand the intent (as would detailed comments) - so I can devise tests (oh yeah, are those available, too?) for the intent - even if it is just me in the debugger. So, I'm for whatever helps me understand the intent of the code - even if there is some slick way to nest everything. I generally prefer breaking nested processes down into multiple steps. Benchmarking rarely shows significant performance differences, for me. (and I've used parentheses 7 times in this one email!) :-) Russ On Wed, Jan 26, 2022 at 4:20 AM Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
-- Russ Whaley whaley.russ@gmail.com
I think that's OK when you can choose when to use parentheses. If you think it would improve readability it is ok to use it, the problem is to be forced to use it even when you think it decreases readability, what I believe to be the case in discussion. On Thu, Jan 27, 2022 at 3:24 PM Russ Whaley <whaley.russ@gmail.com> wrote:
What I like about parentheses (and I hate parentheses) is that it allows me to view/describe the intent of the code. When parens are not used, it is up to me (perhaps years later) to determine whether the original developer (sometimes me) understood (or not) the actual 'order of processing'. Parens - or another vehicle - should help me understand the intent (as would detailed comments) - so I can devise tests (oh yeah, are those available, too?) for the intent - even if it is just me in the debugger.
So, I'm for whatever helps me understand the intent of the code - even if there is some slick way to nest everything. I generally prefer breaking nested processes down into multiple steps. Benchmarking rarely shows significant performance differences, for me.
(and I've used parentheses 7 times in this one email!) :-)
Russ
On Wed, Jan 26, 2022 at 4:20 AM Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
-- Russ Whaley whaley.russ@gmail.com
Hi Kasper, perhaps not what you are asking, but I find that this kind of nesting happens to me when parsing serialized data (web page sources or JSON as in your example). For me, the root cause of the problem is that these data structures are not represented by real classes in my code. I often start with what you are showing in a Playground, but then refactor it into specific parsers for the web sites I am scraping. The parsing selectors go into classes that mirror the parts of the data I am interested in. I find this to be more declarative and maintainable, and more natural in the Pharo environment with its browsers and code extractors. I hope this is of any use :) cheers Siemen On Fri, Jan 28, 2022 at 11:55 PM Vitor Medina Cruz <vitormcruz@gmail.com> wrote:
I think that's OK when you can choose when to use parentheses. If you think it would improve readability it is ok to use it, the problem is to be forced to use it even when you think it decreases readability, what I believe to be the case in discussion.
On Thu, Jan 27, 2022 at 3:24 PM Russ Whaley <whaley.russ@gmail.com> wrote:
What I like about parentheses (and I hate parentheses) is that it allows me to view/describe the intent of the code. When parens are not used, it is up to me (perhaps years later) to determine whether the original developer (sometimes me) understood (or not) the actual 'order of processing'. Parens - or another vehicle - should help me understand the intent (as would detailed comments) - so I can devise tests (oh yeah, are those available, too?) for the intent - even if it is just me in the debugger.
So, I'm for whatever helps me understand the intent of the code - even if there is some slick way to nest everything. I generally prefer breaking nested processes down into multiple steps. Benchmarking rarely shows significant performance differences, for me.
(and I've used parentheses 7 times in this one email!) :-)
Russ
On Wed, Jan 26, 2022 at 4:20 AM Kasper Osterbye < kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
-- Russ Whaley whaley.russ@gmail.com
All the answers have been good. I am somewhat provoked (in the good sense of forcing me to think) by Siemen who sort of think of it as a code-smell. Best, Kasper
On 26 Feb 2022, at 17.14, Siemen Baader <siemenbaader@gmail.com> wrote:
Hi Kasper,
perhaps not what you are asking, but I find that this kind of nesting happens to me when parsing serialized data (web page sources or JSON as in your example). For me, the root cause of the problem is that these data structures are not represented by real classes in my code. I often start with what you are showing in a Playground, but then refactor it into specific parsers for the web sites I am scraping. The parsing selectors go into classes that mirror the parts of the data I am interested in. I find this to be more declarative and maintainable, and more natural in the Pharo environment with its browsers and code extractors.
I hope this is of any use :)
I was bemused to realise that I've actually been programming for literally 50 years. It finally seeped through my thick skull I make enough mistakes that if it's hard to read, I probably got it wrong. And THIS code fragment is hard to read. I don't care if you use parentheses or pipes, it's hard to read either way. Oh, the pipes make the structure easier to see, true. But I am still left guessing what it is FOR. What's it SUPPOSED to mean? And the problem is that there are some abstractions missing. Of course this is JSON's fault. This is the first code smell, and in my experience it is almost ALWAYS a code smell: you are using JSON as a "processing" data structure instead of as a "communication" syntax. Using JSON inside your program, instead of at the edges, means that the semantics gets scattered all over the code, which leads to complexity and inconsistency. The convention I follow is that data structure more complicated than a string or an array of numbers has a Smalltalk class Whatsit>>asJson converts an instance to a JSON value Whatsit class>>fromJson: converts a JSON value to an instance. I'll start with topLevel := TopLevelWhatsit fromJson: someStream nextJson. and from then on I'm working with pure Smalltalk. This has the extra benefit of validating the input right away. I don't want my program running for several hours and then running into trouble because some component of the JSON value is missing or ill-formed. In practice, the time cost of converting JSON to (application- specific) Smalltalk is quite small compared with the cost of reading the JSON in the first place, and there can be seriously worthwhile savings in memory. For this example, we'd be looking at topLevel tree blobs collect: [:each | each path] thenSelect: [:path | path hasAnyExtension: #('md' 'mic')] My Filename class has methods has[Any]Extension:[ignoringCase:] which I have shamelessly exploited here. I like the way that they hide the structure of a Filename. The portmanteau methods Collection>>collect:thenSelect: Collection>>select:thenCollect: have existed in Pharo as long as Pharo has existed. They have two rationales: (a) improve readability (b) permit optimisation by eliminating an intermediate collection. Sequences exploit (b). Sets could too but happen not to. THEREFORE, here are free implementations of Set>>collect:thenSelect: and Set>>select:thenCollect: consistent with Pharo's Set>>collect: collect: collectBlock thenSelect: selectBlock "Override Collection>>collect:thenSelect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | |item| item := collectBlock value: each. (selectBlock value: each) ifTrue: [newSet add: item]]. ^newSet select: selectBlock thenCollect: collectBlock "Override Collection>>select:thenCollect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | (selectBlock value: each) ifTrue: [ newSet add: (collectBlock value: each)]]. ^newSet On Wed, 26 Jan 2022 at 22:19, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
Thanks Richard, I tend to agree with your design experience. As it happens, in my concrete code I get JSON back from a github api which serves me more than I want. I am therefore not interested in making classes for those aspects of the data I throw out. The way I handle this in practice myself is to introduce a local variable for each level. Makes it easier to debug too. Best, Kasper
On 27 Feb 2022, at 00.42, Richard O'Keefe <raoknz@gmail.com> wrote:
I was bemused to realise that I've actually been programming for literally 50 years. It finally seeped through my thick skull I make enough mistakes that if it's hard to read, I probably got it wrong. And THIS code fragment is hard to read. I don't care if you use parentheses or pipes, it's hard to read either way. Oh, the pipes make the structure easier to see, true. But I am still left guessing what it is FOR. What's it SUPPOSED to mean? And the problem is that there are some abstractions missing. Of course this is JSON's fault.
This is the first code smell, and in my experience it is almost ALWAYS a code smell: you are using JSON as a "processing" data structure instead of as a "communication" syntax. Using JSON inside your program, instead of at the edges, means that the semantics gets scattered all over the code, which leads to complexity and inconsistency.
The convention I follow is that data structure more complicated than a string or an array of numbers has a Smalltalk class Whatsit>>asJson converts an instance to a JSON value Whatsit class>>fromJson: converts a JSON value to an instance. I'll start with topLevel := TopLevelWhatsit fromJson: someStream nextJson. and from then on I'm working with pure Smalltalk.
This has the extra benefit of validating the input right away. I don't want my program running for several hours and then running into trouble because some component of the JSON value is missing or ill-formed.
In practice, the time cost of converting JSON to (application- specific) Smalltalk is quite small compared with the cost of reading the JSON in the first place, and there can be seriously worthwhile savings in memory.
For this example, we'd be looking at topLevel tree blobs collect: [:each | each path] thenSelect: [:path | path hasAnyExtension: #('md' 'mic')]
My Filename class has methods has[Any]Extension:[ignoringCase:] which I have shamelessly exploited here. I like the way that they hide the structure of a Filename.
The portmanteau methods Collection>>collect:thenSelect: Collection>>select:thenCollect: have existed in Pharo as long as Pharo has existed. They have two rationales: (a) improve readability (b) permit optimisation by eliminating an intermediate collection. Sequences exploit (b). Sets could too but happen not to.
THEREFORE, here are free implementations of Set>>collect:thenSelect: and Set>>select:thenCollect: consistent with Pharo's Set>>collect:
collect: collectBlock thenSelect: selectBlock "Override Collection>>collect:thenSelect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | |item| item := collectBlock value: each. (selectBlock value: each) ifTrue: [newSet add: item]]. ^newSet
select: selectBlock thenCollect: collectBlock "Override Collection>>select:thenCollect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | (selectBlock value: each) ifTrue: [ newSet add: (collectBlock value: each)]]. ^newSet
On Wed, 26 Jan 2022 at 22:19, Kasper Osterbye <kasper.osterbye@gmail.com <mailto:kasper.osterbye@gmail.com>> wrote: Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
The fact that there is more than you want is precisely why you want to make classes for the data you *do* want. I do have kit for streaming JSON out directly without creating JSON data internally, but I'm still trying to find a good way to stream JSON in creating wanted objects on-the-fly so that the unwanted data are never stored. I think it can be done from the same schema language that I use to generate classes. On Mon, 28 Feb 2022 at 20:09, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Thanks Richard,
I tend to agree with your design experience. As it happens, in my concrete code I get JSON back from a github api which serves me more than I want. I am therefore not interested in making classes for those aspects of the data I throw out.
The way I handle this in practice myself is to introduce a local variable for each level. Makes it easier to debug too.
Best,
Kasper
On 27 Feb 2022, at 00.42, Richard O'Keefe <raoknz@gmail.com> wrote:
I was bemused to realise that I've actually been programming for literally 50 years. It finally seeped through my thick skull I make enough mistakes that if it's hard to read, I probably got it wrong. And THIS code fragment is hard to read. I don't care if you use parentheses or pipes, it's hard to read either way. Oh, the pipes make the structure easier to see, true. But I am still left guessing what it is FOR. What's it SUPPOSED to mean? And the problem is that there are some abstractions missing. Of course this is JSON's fault.
This is the first code smell, and in my experience it is almost ALWAYS a code smell: you are using JSON as a "processing" data structure instead of as a "communication" syntax. Using JSON inside your program, instead of at the edges, means that the semantics gets scattered all over the code, which leads to complexity and inconsistency.
The convention I follow is that data structure more complicated than a string or an array of numbers has a Smalltalk class Whatsit>>asJson converts an instance to a JSON value Whatsit class>>fromJson: converts a JSON value to an instance. I'll start with topLevel := TopLevelWhatsit fromJson: someStream nextJson. and from then on I'm working with pure Smalltalk.
This has the extra benefit of validating the input right away. I don't want my program running for several hours and then running into trouble because some component of the JSON value is missing or ill-formed.
In practice, the time cost of converting JSON to (application- specific) Smalltalk is quite small compared with the cost of reading the JSON in the first place, and there can be seriously worthwhile savings in memory.
For this example, we'd be looking at topLevel tree blobs collect: [:each | each path] thenSelect: [:path | path hasAnyExtension: #('md' 'mic')]
My Filename class has methods has[Any]Extension:[ignoringCase:] which I have shamelessly exploited here. I like the way that they hide the structure of a Filename.
The portmanteau methods Collection>>collect:thenSelect: Collection>>select:thenCollect: have existed in Pharo as long as Pharo has existed. They have two rationales: (a) improve readability (b) permit optimisation by eliminating an intermediate collection. Sequences exploit (b). Sets could too but happen not to.
THEREFORE, here are free implementations of Set>>collect:thenSelect: and Set>>select:thenCollect: consistent with Pharo's Set>>collect:
collect: collectBlock thenSelect: selectBlock "Override Collection>>collect:thenSelect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | |item| item := collectBlock value: each. (selectBlock value: each) ifTrue: [newSet add: item]]. ^newSet
select: selectBlock thenCollect: collectBlock "Override Collection>>select:thenCollect: like OrderedCollection. Beware: 'self species' is often inappropriate, as in #collect:." |newSet| newSet := self species new: self size. self do: [:each | (selectBlock value: each) ifTrue: [ newSet add: (collectBlock value: each)]]. ^newSet
On Wed, 26 Jan 2022 at 22:19, Kasper Osterbye <kasper.osterbye@gmail.com> wrote:
Cheers all
I have noticed that I often ends up with quite a number of nested expressions, for example:
(((json at: 'tree') select: [ :e | (e at: 'type') = âblob' ]) collect: [:e | Path from: (e at: 'path')]) select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
What kind of proposals (if any) have been for a different syntax which could give a more streamlined syntax?
My own thinking has been around an alternative to the cascade semicolon. What symbol to use does not matter for me, but something like json at: âtree' º select: [ :e | ((e at: 'type') = 'blobâ)]º collect: [:e | Path from: (e at: 'pathâ)]º select: [ :p | p segments last in: [ :name | (name endsWith: '.md') | (name endsWith: '.mic') ] ]
Basically, a send the right hand expression to the result of the left hand expression.
Has anyone ever tried this, or is it just one of the many small annoyances best left alone?
Best,
Kasper
participants (13)
-
Esteban Maringolo -
gettimothy -
jtuchel@objektfabrik.de -
Kasper Osterbye -
Marcus Denker -
Richard O'Keefe -
Richard Sargent -
Russ Whaley -
sean@clipperadams.com -
Sebastian Jordan Montano -
Siemen Baader -
Sven Van Caekenberghe -
Vitor Medina Cruz