What's the deal with `X . op . Y`?

Hi, Racket Discourse.

I have some free time this morning, so I thought I'd ask your opinion on using the "infix" form of application, namely (f x y) -> (x . f . y), and maybe waste your time enjoyably.

Personally, I am a big fan. I don't use it everywhere, but it is nigh unbeatable when one needs to make clearer the composition of certain dense expressions.

Now, just this morning I was browsing the r/Racket subreddit and I saw someone mentioning that it's "probably not idiomatic, but it is cool" (I paraphrase).

Is this a common sentiment? It is a part of the language; I would argue that it is precisely idiomatic in the sense that it forms a particular idiom which is, if not "unique" to Racket, at least not very common in other languages (of which I know). I also don't see that it makes code less clear or readable.


Tangent:

I often think of lojban (a constructed language, of which I am not a speaker or reader) when using this form of expression. The language has this concept of verb-relationships, which could be likened to the application of a macro or procedure, the head (applicative?) being the relationship in this case, and its arguments being the noun-components (perhaps, if one wanted to stretch the metaphor, keywords might be like adverbs or prepositions, or whatever).

Furthermore, it allows you to specify the relative ordering of the noun-components using something like prepositions to indicate the positionality, so in "Racket-y" terms:

(rel #:2nd noun #:1st noun) and so on.

And, as with Racket, the verb-relationship can go almost anywhere in the sentence, so:

(#:1st noun . rel . #:2nd noun) makes sense in this context.

This maps more or less clearly to procedures, but with macros this is not exactly (ahem) applicable, because the syntax of the macro is more positionally dependent, and cannot necessarily be mangled in this way.

In a recent adventurous attempt to mess around with alternative conditionals, I became newly interested in using verbs and nouns to describe code.

In a somewhat related post, @lexi.lambda made a very interesting point I would probably not have been able to articulate myself:

One can group macros into two categories (not absolutely, of course, but for the sake of argument), namely, those which abbreviate intent, and those which abstract intent.

The difference being that an "abbreviation" is context-free (plus-minus) and "exactly equivalent" to the more verbose form of the expression; while "abstraction" is more like a black-box in the sense that it takes some inputs, and based on what it has been defined to do and possibly using unseen information, will then produce the intended result, although the original structure may or may not be recoverable or even recognizable from the end product.

Where am I going with this? Well, as an example, I don't like putting my handlers at the top of my code when dealing with possible exceptions. So much so, that I have recently taken to using this custom form:

(define-syntax-rule
  (body ...
   . with-exceptions .
   ([exn handler] ...))
  #;becomes
  (with-handlers ([exn handler] ...)
    body ...))

So that I may write this, instead:

((let loop ([deadline #false])
   (sync
    (if deadline
        (handle-evt deadline (thunk* (loop #false)))
        (handle-evt
         checkup
         (lambda (_ timestamp)
           (log-application-info "alive, next check in 10 minutes")
           ; create an alarm for 1 second after the timestamp
           (define deadline (alarm-evt (+ (* timestamp 1000) 1000)))
           (loop deadline))))))
 . with-exceptions . ; HERE I AM
 ([exn:break?
   (lambda _
     (log-schedule-info "stopping")
     (shutdown-scheduler)
       
     (log-application-info "stopping")
     (shutdown-log-receivers))]))

I am wondering, if it would not be useful (or maybe someone has even already done this), to be able to specify in a more general manner where what is supposed to go, and in what order.

Obviously, the syntax-rule is doing that in this instance, but it seems wasteful? that I have to write a "new form" to achieve this flexibility, which someone else has to go and inspect to remove the "black-box-ness" and discover what it does (which is normal, but frowned upon--not everyone feels the way I do about the way things should be presented).

I cannot just use (x . with-handlers . y) because the position of the handlers remains the same, so I have to resort to the syntax-adjustment.

What if there were a related mechanism to (x . f . y) which allowed one to specify that a particular expression is supposed to be the n-th thing in a particular form, without necessarily having to modify or wrap the original in extra machinery. Almost like a keyword, but perhaps not because this might be confusing.

In the example of with-handlers then, maybe one might write:

(#:2nd body . with-handlers . #:1st handlers)

instead of having to define manually a new macro (abbreviation) which simply rearranges the resulting code.


What do you think; have you come across something like this before, and would you think it to be a "bad idea" if someone proposed it?

P.S.
All my ideas are half-baked; so, it could be cake, or it could be fake. You decide.

1 Like

Hello,
i'm not sure what you want to do,
i used a few the (X . op . Y) Racket notation , and wanted to made it more easy, more readable as the mathematic notation,so
i have an infix lib that allow (X op Y) but with the restriction for now that op must be in a list of defined operators (generally arithmetic, so your example : (x with-handlers y) would actually not work with my lib) but this can easily be extended to arbitrary any procedure in the list (of operator/procedure recognised). Would it be usefull? (it is something i have in the head since a while: to be able to update dynamically the operator/procedure list , even if i think ,this only could be done at the beginning, and before any syntax transformers get in action...i'm not sure of those last points,it is too technical to think it now reliably)

2 Likes

I'm not a fan of it. That's partly because of the headache it makes for Resyntax. It's difficult to tell the difference between (a . op . b) and (op a b), so on more than one occasion a Resyntax refactoring rule has gone awry because it tried to reactor code written in the infix style and broke. Racket's infix reader breaks a cardinal rule assumed by Resyntax: that the order of subexpressions in an original syntax object matches the order of their source locations.

But I have less selfish reasons to dislike it. I think the designer of an API is in a far better place to decide whether or not it should be written infix than the user. That's why I added the compare-infix macro to Rebellion's comparator library, for example.

2 Likes

Hi, @damien_mattei.

It could probably be described as an ad-hoc fixity operation, but I think my idea of the positional system is even more arbitrary.

Messing around a bit during lunch, this is an idea of what I mean, although the mechanism does not even use the (x . f . y) form of expression!

We'll just imagine that #:~ verb is actually . verb . for the sake of argument.

#lang racket

(require
  (for-syntax
   syntax/parse))

(begin-for-syntax
  (define idx~ #px"^([\\d]+)~$")
  (define (id->string id) (symbol->string (syntax->datum id)))
  
  (define-syntax-class pos
    #:datum-literals (*~)
    #:attributes (idx)
    
    (pattern (*~ ex ...)
      #:with idx #'(+inf.0 ex ...))
    
    (pattern (n:id ex ...)
      #:do [(define ?idx (regexp-match idx~ (id->string #'n)))]
      #:when ?idx
      #:with pos (string->number (cadr ?idx))
      #:with idx #'(pos ex ...)))

  (define-splicing-syntax-class rel
    #:attributes (verb)
    (pattern {~seq #:~ verb}))

  (define (seq indexed ordered)
    (let loop ([position 0]
               [indexed  (sort indexed < #:key car)]
               [ordered  ordered])
      (cond
        [(null? indexed) (map list ordered)]
        [(null? ordered) (map cdr  indexed)]
        [else
         (define head (car indexed))
         (cond
           [(= position (car head))
            (define tail-i (cdr indexed))
            ((cdr head)
             . cons .
             (loop (+ position 1) tail-i ordered))]
           [(< position (car head))
            (define tail-o (cdr ordered))
            (`(,(car ordered))
             . cons .
             (loop (+ position 1) indexed tail-o))]
           [else
            (error 'pun-seq "sequencing error in indexed: ~a" indexed)])]))))

(define-syntax (prosa stx)
  (syntax-parse stx
    [(_ (~alt (~once :rel) :pos ord) ...)
     #:with (arg* ...) (datum->syntax
                        stx (seq (syntax->datum #'(idx ...)) (syntax->datum #'(ord ...))))
     #'(verb (~@ . arg*) ...)]))

;; AN EXAMPLE
(prosa
 (*~ (println 'me-last))
 (2~ (println 'then-me) (println 'and-me!))
 (1~ (println 'me-first!))
 #:~ with-handlers
 (0~
  ([exn:fail?  (lambda (x) 'oops)]
   [exn:break? (lambda (x) 'what)])))

#| =>
'me-first!
'then-me
'and-me!
'me-last |#

I wouldn't really call this an improvement, though, since the additional noise introduced by the form is counter to the point.

Re the Resyntax (haha) note: I have not used it per se, but I did notice some weirdness recently when trying to use syntax->string on "infixed" expressions.

You make an interesting point about the intent of the designer. It makes me think of the craze at one point for "interactive films", which was winding down around about the time they released the X-files "interactive movie".

On paper, this sounds great: Who wouldn't want to interact with their favourite characters in the setting of a film, deciding where the plot goes? But practically speaking, you end up getting the worst parts of either the game or the movie.

Food for thought.

2 Likes

sorry @bakgatviooldoos
i have not enought knowledge in Racket special syntax extansion to understand the code and answer.

Noticed :

it changes the order of the print ... i have little thoughts... (if prints were disks ...) i feel like this student at Lycée (high school) at the end of the lesson, after the math teacher explained during one half-hour the Hanoi problem tower and his solution by recurrence, a classmate told me hilarous, that another student went to the teacher to ask "why don't we move the whole stack of disks at once?"
ah ah ah
(too bad: i haven't see the face of our math teacher at this moment , i was already out of the classroom, but i will never forget this funny story)

3 Likes

My sides :joy:.

Yeah, it seems kind of silly, though. I think perhaps the mismatch is because I am looking at the story from a deficient perspective.

I mention being interested in the concept in a somewhat linguistic manner, especially after reading this page.

But to make the point, I realize an interesting thing about natural, spoken languages, is that the constraint of having to convey meaning without having the meaning written down, to a listener with finite attention, breeds interesting ways of getting around this, in terms of lexical structure, and so on.

So, it is helpful to have different orderings of words mean different things, for example, or have ways of indicating that the speaker has changed the accepted ordering under some known rules to convey a meta-intention, as I am suggesting to an approximation.

But with code, especially very malleable code, this isn't as interesting, because everything is right there in the file, and what matters more is how things fit into one another and what they change into over time.

I guess, when presented with the problem of flipping the tower, it helps to have an interesting set of constraints--otherwise, why wouldn't you just move the whole stack at once?

I still think there is something to the idea, but the ways I have thought of looking at it, have not yet been the right ones.

3 Likes

interesting idea and fun it is with Perl, i have made a few Perl program in the past and the style was so concise that a few month later it needed a lot of time to understand it again. Even in Latin it will have be more understandable. :slight_smile: But i like the idea to search for different way to express code. About the X . op . Y the style is too far away from mathematical convention, this is the problem with scheme , for this reason i develops the infix notation in scheme. (scheme+)

Regarding your comments on Perl: 93% of Paint Splatters are Valid Perl Programs | Colin McMillen

1 Like