Back

A trick to have arbitrary infix operators in Python

238 points2 yearstomerfiliba.com
nneonneo2 years ago

One of my favorite (ab)uses of operators is the "q" package for debugging: https://pypi.org/project/q/

You `import q`, then you can slap `q/` or `q|` in front of an expression to log the value (q/ for high precedence, q| for low precedence). These days, `f"{x=}"` is probably preferred, but I still wind up using q occasionally because of its simplicity.

ziml772 years ago

Just last week I was thinking that it would be great if Python had a way to print both the name and value of a variable without having to repeat the variable name. I checked the formatting mini-language spec and when I didn't see it there I assumed it didn't have it. I'm glad to know now that it does have that feature!

renox2 years ago

There are several versions of the "the formatting mini-language" and the documentation that Google return first is for an old one which didn't have this feature :-(

zestyping2 years ago

Aw, it's such a delight to open Hacker News and see this at the top of the comments! Glad you're enjoying q.

I still use the @q decorator pretty often; it's really nice to be able to trace the arguments and return values of specific functions.

Siira2 years ago

I use icecream: `ic(debug_expr)`. Not as concise, of course.

jaytaylor2 years ago

Please read the comment below in the gentlest, friendliest tone possible, because my goal is to considerately inform and persuade you, dear writer :)

HTML links make it 10-100x more likely other readers will learn about the thing being mentioned (and thus help increase awareness and adoption of good tools and resources).

https://github.com/gruns/icecream

Example:

    from icecream import ic

    def foo(i):
        return i + 333
    
    ic(foo(123))
Prints

    ic| foo(123): 456
paimoe2 years ago

Software library naming has always been terrible and mostly un-searchable, so links definitely help

musicale2 years ago

For people who know how to use search engines, PP's version may be better since it wastes less vertical space on HN.

jounker2 years ago

Thank you! I’ve been trying to remember this package’s name.

raymondh2 years ago

This is a pretty cool recipe.

The precedence is determined by the overridden operator. In this case the "|" operator has low precedence.¹ For a high precedence operator, override "%" with __mod__ and __rmod__:

    4 |add| 5 %mul% 6 |add| 7    # mul happens before add
¹ https://docs.python.org/3/reference/expressions.html#operato...
CornCobs2 years ago

Nice! It just so happens that %% is used to denote infix operators in R (which can have arbitrary names). Like the %in% operator in base, or the famous pipe operator %>% from magrittr

kragen2 years ago

Ooh, I had no idea. That might swing the balance in favor of %beside%, since R is super popular.

dragonwriter2 years ago

> As you may already know, there are 3 kinds of operators calling-notations: prefix (+ 3 5), infix (3 + 5), and postfix (3 5 +)

There’s more than that; Raku, for instance, recognizes two more classes: circumfix (pair of symbols defining operator surrounds operands) and postcircumfix operators (like circumfix, but with an additional first operand outside of a preceding the paid of symbols), and lets you define new operators in any of the five notational classes.

bradrn2 years ago

It goes further. Agda allows mixfix operators [0], which allows expressions to be arbitrarily interleaved with names and symbols, e.g.:

    if_then_else_ : {A : Set} → Bool → A → A → A
    if true then x else y = x
    if false then x else y = y
[0] https://agda.readthedocs.io/en/v2.6.0.1/language/mixfix-oper...
agumonkey2 years ago

Hey that's lifted from Smalltalk isn't it ?

dragonwriter2 years ago

Smalltalk has functions with keyword arguments that look kind of like mixfix operators.

travisgriggs2 years ago

I miss keyword syntax. It has its warts, but I liked the clarity better than the whole funarg() list mess.

agumonkey2 years ago

funny, googling mixfix taught me racket also had something for it https://docs.racket-lang.org/mixfix/

munificent2 years ago

There's also mixfix where the operators are interleaved between the arguments, of which there are more than two. The well-known one is C's conditional operator:

    a ? b : c
This is a single operation `?:` that takes operands a, b, and c.
account422 years ago

Except that the ternary operator in C and C++ is not really an operator but a control flow construct.

HelloNurse2 years ago

It's an operator because it's functional (although often with side effects): three expressions go in and one expression goes out. Control flow constructs deal with statements instead.

munificent2 years ago

It's an operator in the syntactic sense in the same way that "=", ".", and "||" are all operators.

agumonkey2 years ago

Some examples https://docs.raku.org/language/operators#Operator_classifica...

ps: we're going back to APL it seems

powersnail2 years ago

This is hilarious. Probably nobody is going to use this in production, (infix operator is a bad idea outside of math operations, in my opinion), but it's interesting to see how much you can coerce a language into almost a different language altogether.

This is on par with the "Generics in Go"^1 hack. Is there a list of this kind of funny language hack somewhere?

[1]: https://www.reddit.com/r/rust/comments/5penft/parallelizing_...

dataflow2 years ago

> infix operator is a bad idea outside of math operations, in my opinion

What about string + string?

powersnail2 years ago

I was too rash with my comment. Indeed, string + string is also quite intuitive.

dataflow2 years ago

Haha no worries. I think there are more cases outside (what ordinary folks would call) "math", where infix operators make sense; they're just not as common or obvious. Like one example is when you want to build expression trees; you're not actually doing math there, but merely creating a representation of the very expressions you're writing. Another one is &, |, ^, ~ for enum flags (which you can define bitwise so it's "math" in that sense, but which you might want more domain-specific logic for). Another one is overloading <, =, > for arbitrary objects. And so on.

I think the real rule is "it should be intuitive rather than confusing". :-)

naniwaduni2 years ago

It's always been a bad idea, because concatenation is a natural product, not a sum. We should write string * string.

lisper2 years ago

> concatenation is a natural product, not a sum

Huh? Why?

+3
dataflow2 years ago
dataflow2 years ago

I guess, but that ship sailed already.

cttet2 years ago

not quite, since `a + b != b + a` feels a bit weird.

dataflow2 years ago

I think you missed the other thread... https://news.ycombinator.com/item?id=30081517

+1
cttet2 years ago
UncombedCoconut2 years ago

FWIW: A pattern along these lines is in B of A's Quartz, allowing the construction of table filters like "Where('colname') <<inlist>> ['cat', 'dog']".

bayesian_horse2 years ago

I can see how it could be useful in actually increasing readability. Just as always, use with caution.

yccs272 years ago

This is a super neat hack. Unfortunately, this kind of custom notation can make a project much less readable to outsiders who are only familiar with the base language. Probably the reason why Python disallows custom operators.

Incidentally, the Wolfram Language has a similar notation for arbitrary functions: a~Plus~b.

hansvm2 years ago

I wouldn't be totally surprised if the reason Python disallows custom operators is because it either requires a lot more context in the parser or a lot of care in how they're used (or a hack re-using existing operators like TFA). Neither option is without its costs. Languages that have them usually opt for the former.

E.g., in Python `foo|bar` is totally unambiguous right now, give or take some nuance as to whether it's in a string/comment/whatever, but outside that tiny bit of context it's clear what's a variable and what's an operator. As soon as I can define `oo` and `|ba` as operators though it's a lot less clear what's going on, and all parsers (including editors, not just the cpython interpreter) need to be able to understand those semantics to work reliably.

Edit: I'm having a tiny bit of trouble finding it, but my memory just came back to me, and there's a PEP floating around where Guido's rationale was just that there wasn't a solid use case so it wasn't worth implementing. Later the numpy folks introduced the @ symbol as a solid use case (matrix multiply), but nobody could come up with a sufficiently convincing argument for arbitrary operators -- we gained the @ operator and don't have the rest.

srcreigh2 years ago

Racket does too.

    (when (0 . < . x)
         (displayln "positive"))
Although in Racket you could also add your own syntax.
anon_123g9872 years ago

Infix operator in R:

  > '%mult%' <- function(a,b) a*b
  > 3 %mult% 5
  [1] 15
Using infix operators as functions:

  > '*'(3,5)
  [1] 15
mhitza2 years ago

I just assumed Racket supports curly infix notation, but looking just now I see that you need to use the infix pragma and wrap things with @{}

I liked GNU Guile for the fact that it's enabled by default, thus the following is a bit more readable in my opinion

    (when { 0 < x }
      (diplay "positive") (newline))
soegaard2 years ago

FWIW a user can add this syntax himself to a language if he needs to.

A hypothetical `curly-infix` language could be used like this:

    #lang curly-infix racket
    (define (fact n) (if (= n 0) 1 {n * fact(n-1)}))
The reader for `curly-infix` can use a read-table to alter how `{<infix-expression>}` is read. The result can the be passed to the language after the `#lang curly-infix` which in the example is just `racket`, but could be something else.

Hmmm. Maybe I should make a `curly-infix` wrapping `infix` from my `infix` package.

bhrgunatha2 years ago

I've always wondered why that was added. It feels to me against the idiom (argot?) of any lisp. It's not composable either.

    (0 . < . x . < . 12)    
    read-syntax: illegal use of `.`    
Typing those extra '.'s gets annoying very quickly too.
srcreigh2 years ago

My guess is Racket/PLT scheme's history as a language for teaching programming to students.

Racket has some other things that are non-lispy. Support for alternate (maybe non-SEXP) syntax readers and for loops come to mind.

At least it's all simple. Sometimes terrifyingly simple, but simple.

They're useful too. Non-SEXP reader could be used to create a non-infix dialect of Racket. Loop macros like for/or can be used as a more ergonomic way than recursive functions to walk a list to find/transform one of its elements.

soegaard2 years ago

It was an experiment - I think the general consensus is that it was a bad idea.

The double dot notation is mostly used for inequalites:

    (x . < . 3)            instead of  (< x 3)
and for function contracts:

    (any . -> . boolean?)  instead of  (-> any boolean?)
kazinator2 years ago

MacLisp had an extended dot notation similar to this for hunks:

https://www.maclisp.info/pitmanual/hunks.html

HerrMonnezza2 years ago
bhawks2 years ago

That's gross. I love it.

I'd only reach for it for certain types of DSL where infix greatly eased readability (perhaps templating). I would also run it by a handful of people before committing it to verify I haven't gone completely insane.

Fwiw kotlin supports infix operators as well and they shine in dsl use cases.

carapace2 years ago

I recall someone doing this using the shift operators <<, >>:

    a <<op>> b <<op>> c
assbuttbuttass2 years ago

This is pretty neat, but I always find I prefer the simplicity and extensibility of lisp's syntax, despite the difference to math notation. For example, the author's example of add(add(add(5,6),7),8)... is just (+ 5 6 7 8 ...).

Haskell supports custom infix operators, but you end up having an unreadable soup of <* <$> <$ >>=

toomanydoubts2 years ago

>but you end up having an unreadable soup of <* <$> <$ >>=

True to some extent. After learning haskell, I find these operators very readable actually.

dataangel2 years ago

How do you Google them? As a Haskell newbie this was a big problem for me.

bobbylarrybobby2 years ago

Often, by type signature using Hoogle

yoyohello132 years ago

In ghci you can do:

Prelude> :doc <$>

and

Prelude> :t (<$>)

pizza2 years ago

hoogle or hoogle+

joppy2 years ago

Haskell also supports using any old function as an infix function by putting backtics around it, for instance (x `div` y). It’s a nifty feature that can improve code readability (especially in more mathematical contexts) when used well, I wish more languages had it.

d0mine2 years ago

Python supports add(5,6,7,8, *numbers) syntax.

Depending on dialect there can be reader macros [its analog] in lisp which allows defining your own DSL that can hurt readability even more than operator overloading

zomglings2 years ago

This is indeed a clever and nice trick.

That said, I doubt I will ever use it professionally. In fact, I would probably reject any changes in code review that used this kind of trick.

smaudet2 years ago

> In fact, I would probably reject any changes in code review that used this kind of trick.

Why not? Operator overloading (if used well) is invaluable to expressiveness, and at least IMO related to a strong type system.

Abstract Algebra defines a whole formal nomenclature around defining operators such as add, subtract, etc., for _arbitrary structures_.

Sure, it is complex, but good _engineering_ sometimes requires complexity.

For real world examples, I would take numpy - its custom arrays are a bit jarring to newcomers at first, to be sure, but it is a damn _useful_ library.

You can implement everything numpy does with "normal" functions, but you will end up with either unmaintainable nonsense or spaghetti code, e.g. in pseudo-code, it is clear what this does:

Matrix Result = Matrix1^2+Matrix2^2

Versus:

Matrix Matrix3 = Square(Matrix1); Matrix Matrix4 = Square(Matrix2); Matrix Result = Matrix1.Add(Matrix4);

This code is intentionally incorrect - can you spot the difference immediately? It's trivially fixed, but only by running the processing stack in your head. This is a very common sort of error. People prefer Infix operators and then say that function soup is the only way to go...

Note also that, while tests might catch that your code is wrong, you have to know what the code should be doing in order to spot that. It may even be easier to verify by using e.g automated proof verification.

bayesian_horse2 years ago

That's pretty nifty...

It also doesn't require you to modify/extend the operands' classes. As always, be responsible with such magic. Tools (and the compiler for error messages) won't recognize that |add| is supposed to be one unit. Autoformatting may extend it to | add | or even break it up into two lines...

kragen2 years ago

I can't believe I never knew about this. This is super cool, and although yes it's potentially subject to readability problems, it allows you enough freedom to design your desired notation that you might be able to come up with something that's more readable than standard Python for a given embedded DSL rather than less, in particular for associative operators. And it's way easier to search the documentation for than non-alphabetic operators.

Consider Tk-style page or GUI layout, as in http://canonical.org/~kragen/sw/dev3/alglayout.py (in OCaml I used `:` and `..` http://canonical.org/~kragen/sw/dev3/alglayout.ml):

    pageheader |above| (leftsidebar |beside| vstack(*abstracts)) |above| footer
My Python version used `|` and `-` and `~`, which I think is super confusing, and I think would probably be a lot better with named infix operators like the above:

    vr, hr, dhr = [~String(s) for s in '|-=']
    left = (('Chapters' | String('')) - hr -
            (chapno | vr | [t | ~String(' . ') for t in titles]))
    pages = 'Page' - hr - pnos
    table = ~('*' - hr - '*' - ' ') | ' ' | dhr - (vr | left | pages | vr) - dhr
This produced this layout:

    * ========================================================================
    - |Chapters                                                          Page|
    * |----------------------------------------------------------------------|
      | 0.|The unchained desert of the singing perfume. .  .  .  .  .  .    1|
    * | 1.|La brisa ilusa de la neblina abandonada. .  .  .  .  .  .  .    43|
    - | 2.|The smiling murder within her young daughters. .  .  .  .  .    80|
    * | 3.|Aquella hija tortuosa de su precipicio árido. .  .  .  .  .  .  96|
      | 4.|False villages of the foolish monks. .  .  .  .  .  .  .  .  . 114|
    * | 5.|El roble sacrílego de mi sacerdote desmoronado y enlutado. .   124|
    - | 6.|The brazen river of his filthy, fallen ivory. .  .  .  .  .  . 166|
    * | 7.|The laughing monster of his humble scream. .  .  .  .  .  .  . 203|
      | 8.|The rough tattoo of his unchained abomination. .  .  .  .  .   219|
    * | 9.|Los perfumes silenciosos de un árbol monstruoso muriéndose. .  237|
    - |10.|The sweet tower of the smooth fire. .  .  .  .  .  .  .  .  .  247|
    * ========================================================================
Such named infix operators would also take care of the coercions; instead of writing `('Chapters' | String(''))` I could write `'Chapters' |beside| ''` and let `beside` do the coercion.†

— ⁂ —

Another application for weird infix operators is computation on a lattice (in the partial-order sense):

    w |meet| x |meet| (y |join| z)
Boolean bitvectors are a lattice, so it might be forgivable to overload the bitwise operators for general lattice elements, but I would argue that the following notation is less readable than the above:

    w & x & (y | z)
— ⁂ —

How about binary relations, as in http://canonical.org/~kragen/binary-relations?

    neighbor = (x |compose| near |compose| x.T) |union| (y |compose| near |compose| y.T)
You could usefully apply this to composing SQL queries, for example. Instead of writing

    """SELECT clients.name, orders.id
    FROM clients, orders
    WHERE clients.id = orders.clientid
    ORDER BY clients.name
    """
you could reasonably write

    clients.name |product|
     (clients.id |compose| orders.clientid.T
      |compose| orders.id)
or, with better namespace management, maybe just

    name |product|
      (clients.id |compose| clientid.T |compose| orders.id)
— ⁂ —

Maybe 3-D modeling, as in https://dercuano.github.io/notes/algebraic-graphics.html? Extrusion along a path is associative even if it contains rotation, right?

    square = xline |extrudedby| yline
    cube = square |extrudedby| zline
    twistedcube = square |extrudedby| (zline |during| zrotate(2*math.PI))
— ⁂ —

Maybe melodies, as in http://canonical.org/~kragen/synthgramelodia and http://canonical.org/~kragen/sw/aspmisc/gramelodia.py? The operators defined there aren't associative but they could be.

    theme = bell.twice |then| bell.fifthlower |then| rest
    tune = theme.twice.octavehigher |during| theme.slower
    rhythm = boom |then| whack |then| boom.twice.faster |then| whack
    play(rhythm.repeatedly |during| tune)
Of course the real test is not whether you can write code to say what it means straightforwardly; that's necessary but not sufficient. The real test is whether you get reasonable error messages when you write something like

    play(rhythm.repeatedly |during| then| tune)
____

† In that particular case, if the code had a purpose other than being an experiment in ultra-minimalism, `ljust('Chapters')` would have been better: the particular goofy thing I was doing was that string boxes right-justify their strings if they get allocated more space than they need, while horizontal concatenation boxes allocate any extra space they receive to their rightmost box, so you can get left-justified strings by just stacking an empty string box to their right.

globular-toast2 years ago

This is why we love Python! Yeah, I know we should do boring stuff for production and not get creative, but creativity is the real joy.

> Prefix (as well as postfix) operators are used in languages like LISP/Scheme, and have the nice property of not requiring parenthesis

This reads quite funny because Lisp is famous for its parentheses! The missing bit is parentheses aren't required but only in the binary case. Lisp supports n-ary operators hence why it needs parens.

gilch2 years ago

You only need the parens when the arity can vary. It doesn't have to be binary. Red and REBOL are prefix languages like Lisp, but without the parentheses, and their calls can have more than 2 arguments. You do have to know the arity of each function to make a parse tree, so the arity of any given function is fixed.

joe_the_user2 years ago

People are used to math infix operators and can use them efficiently.

But working with infix operators in other contexts would be a complete nightmare. Limiting what a given expression can "bind" to, what it can form a large expression with, is a way to limit the processing needed to understand a given piece of code. Being able to say one thing a hundred different ways isn't good if the read has to search for a hundred different maybe-meanings a given string might have.

ogogmad2 years ago

Sometimes it's nice to be able to do piping:

  object | dothis | dothat | dosomething
I suppose the piping operator is infix. I feel like some of the advantages of the notation

  object.f()
over

  f(object)
in object oriented programming are due to this.
andreareina2 years ago

An interesting feature of D is Uniform Function Call Syntax (UFCS) where any function with first parameter of type T can be called as a method on T: https://dlang.org/spec/function.html#pseudo-member

codethief2 years ago

That is indeed an interesting feature! I wish more languages implemented it!

In Python, of course, one argument against implementing it is that there already is the getattr magic method (and the getattribute one), so even if it'd be possible to implement UFCS without breaking changes, it would at least complicate the way attribute access works even further. But, of course, one could always introduce a new operator/token (instead of ".").

xigoi2 years ago

Other languages I know of that have this feature are Nim, Koka and VimScript.

runevault2 years ago

I love |> in f# because of this type of thing, piping operations together instead of nesting or the like just reads so much cleaner. And yes in cases where they are all methods on the type you can have the function return self and then do value.Func1().func2().func3().etc() but only if they are methods on the class.

But I also don't care for arbitrary infix operators (which I'm pretty sure f# supports but I haven't used it in a way where I run into them). I know I'm crazy but I almost wish we did away with ALL infix operators, even stuff like +/-* and just do everything as functions. You know, like Lisp :P Edit: I guess I should clarify as it sounds back and forth. For traditional stuff everything functions, with exceptions for things like piping that are purely about chaining operations together (both |> and >> in f# are examples of infix that's specific use case is different enough from say +-/* I think they warrant it).

ziml772 years ago

> but only if they are methods on the class

C# got around this by introducing extension methods. You put static methods inside any static class whose namespace you've imported and label the first parameter with `this` before the type name. Then you're able to call that static method as if it was an instance method on anything that is or implements the type of the first parameter. Basically it's an explicit version of D's unified function call syntax.

I think the pipeline operator is nicer, but extension methods aren't a bad solution.

runevault2 years ago

Sure, you can fake it to get to the same place. And I've abused extension methods a lot, including on interfaces before c# made default implementations on interfaces a more acceptable thing with c#... 9 I think?

eyegor2 years ago

F# does allow custom infix operators, but of course prefix + piping is generally cleaner. Only time I've used custom infix are for implementing a "null coalesce" operator and for overloading math ops for vectors and matrices. The null coalescing is generally only used for code that interops with c# since f# offers non null guarantees for most types.

runevault2 years ago

I can see wanting null coalesce in interop scenarios. I know I use it a lot in C#.

OJFord2 years ago

> I suppose the piping operator is infix.

It's more like composition isn't it?

    object | dothis | dothat
Is pretty much

    dothat(dothis(object))
If stdout is considered the return value at least.

The pipe itself is just a sort of.. notation that a postfix function follows? Whereas in mathematics the function is prefixed, and the notation itself is infix and optional. (That's a knowingly rough and lazy description that any mathematician will hate...)

kragen2 years ago

I think

    dothat(dothis(object))
is more like

    < object dothis | dothat
or

    dothis object | dothat
while the full composition

    object | dothis | dothat
is more like

    dothis(dothat(object(stdin_and_everything_else)))
+1
OJFord2 years ago
visarga2 years ago

Is it possible to do this in Python, such as:

instead of: len(my_list)

to do: my_list | len

of course you can still do: my_list.__len__() but it's ugly

smaudet2 years ago

> Being able to say one thing a hundred different ways isn't good if the read has to search for a hundred different maybe-meanings a given string might have.

This the absurd notion that there is some one "best" way to do things...hard core disagree.

If this were true, there should only be one library, one way of doing anything, one codebase, and therefore only one program, nay even there should only be one programmer, and Python should be a pet project of this one programmer doing everything one way...

The fact that you can obfuscate python, means python has failed its ethos in this respect (https://pypi.org/project/python-obfuscator/).

Compatibility is good, a thriving ecosystem is better.

Critically, a thriving ecosystem is made by a hundred different ways of doing something, all doing something similar, not by making everything similar, so that the first time if fails, its game over.

yccs272 years ago

Yeah, relying on operator precedence makes for some of the most infuriating to read code.

abrudz2 years ago

Yeah, infix doesn't go well with that. Even traditional mathematical notation ends up being ambiguous because of it. However, if all infix operators are given equal precedence, it all becomes very neat. See APL (https://apl.wiki) for an example of that.

maximus-decimus2 years ago

At the same time Q (which is based on apl) only allows built in functions to be infix because otherwise your code gets unreadable Especially if you also have prefix notation.

in `one two three`, are one and two functions using prefix notation or is two an infix function that takes both one ant three as arguments?

It's not too bad if you have a small amount of infix functions to remember, but I don't want to second guess everything while reading code.

mkl2 years ago

Code written by people who don't know operator precedence and so put brackets around absolutely everything is significantly harder to read IME. I've seen students end up with "))))))" at the end of a line. Brackets are better as a signal that something unusual's happening.

draegtun2 years ago

I remember seeing the original Activestate post and porting it to Perl. But it was 12 years ago so took me a while to find it :)

https://gist.github.com/draegtun/540493

gpderetta2 years ago

This trick sometimes pops up in C++ as well, although I never seen it used in production.

I think the earliest use I can remember is with the experimental FC++ library[1], circa 2001.

[1] https://yanniss.github.io/fc++/

steerablesafe2 years ago

> although I never seen it used in production

Well, of course boost has it, they use `<` and `>`:

[1] https://www.boost.org/doc/libs/1_78_0/libs/hof/doc/html/incl...

aflag2 years ago

It’s a neat trick, but I feel like this can break in surprising ways. After all, you’re thinking of |fun| as a single thing, but in reality there are a couple calls in there and the precedence of something like 3 % 2 |add| 5 - 2 could be unintuitive.

andreareina2 years ago

Same thing happens in pandas when composing conditionals in a selection operation. You need to use `|` instead of `or`, but its precedence is low so you need to add parens:

    df[(df["col1"] == foo) | (df["col2"] == bar)]
jonnycomputer2 years ago

Very cool, but its the kind of clever stuff I hate to see in code, unless there is a really compelling use case. Maybe for piping with pandas similar to dplyr

bayesian_horse2 years ago
atorodius2 years ago

Pretty neat trick. I often wish for a built in way to do a bit more operator magic, but I can see why it would be bad to have it.

account422 years ago

Hah, I did this in C++ once as a joke for calling std::swap() as

  a <swap> b;
keeganpoppen2 years ago

what a magnificent trick! feels even more subversive in a "no fun allowed" language / ecosystem as well. i doubt i'll ever have a good reason to use it, but that doesn't mean that i will never use it (muahaha).

cttet2 years ago

We could have sacrilegious Python version that put up all the stuffs lol. Such as multiline lambda :p here:

  def begin(\*args):
    return args[-1]

  begin(
      func := lambda x, y: begin(
          z := int(input()),
          x + y + z
      ),
      func(1, 2)
  )
pizza2 years ago

That's awesome! My personal opinion; lambdas in Python are really limited relative to their potential. We should be better able to exploit the contrast between "a lambda is an expression" and "a def function is a statement."

It would be cool if they could be modified just like classes, I think - keep the original lambdas, and add something else nice like Haskell's Arrows.

prionassembly2 years ago

    key_value = Infix(lambda u,v: dict(zip(list(u),list(v))))
Let's goooo.
nojs2 years ago

> Now imagine I have a function, add(x,y), and I have an expression like add(add(add(5,6),7),8)... wouldn’t it be cool if I could use infix notation here?

Cool trick, but you can also just use def add(*args).

CornCobs2 years ago

I've never seen this name-in-operator syntax before. Does this work with all 2-character operators? Where is this documented and what are the usecases?

dragonwriter2 years ago

It's a hack using a single character binary operator and the way it's implemented for the type that serves as the main “operator” on between the paired use.

> Where is this documented

Insofar as it is, on the page linked. The underlying operator overloading mechanism that his leverages is documented in the official Python docs.

CornCobs2 years ago

OHHH. Looking at the Infix class carefully I realize it's not actually |add| as a single operator but using the right or __ror__ to partially apply the function, then using the left or __or__ to fully apply it. Wrapping it up in a decorator class is really neat!

amelius2 years ago

What's the performance penalty?

kragen2 years ago

Pretty bad, but in a lot of cases you can use this to set up some kind of dataflow graph you use for long enough to amortize the overhead.

zomglings2 years ago
kragen2 years ago

Yeah, it's a very widely used technique to use slow scripting languages (with or without operator overloading) to set up some kind of graph describing a computation that takes a lot longer to run than the scripting-language statement takes to interpret. In addition to Beam, we could mention the Unix shell, Pandas, Numpy, TensorFlow, SQL, SQLAlchemy, APL, Lush, PIL, CSS, some Linux system calls like sendfile(), maybe Apache SPARK, etc.

bayesian_horse2 years ago

You wouldn't use it in any situation where performance is important...

vasili1112 years ago

I whish Python had piping so we do not need to do something like that.

animal_spirits2 years ago

(2012)

barrenko2 years ago

Just stop.

lostmsu2 years ago

I think this would work in many languages with operator overloading. Same thing in C#: https://gist.github.com/lostmsu/6922f6e1f5b4f07c88358e92d6e8...

Not sure yet if it is possible to make this work for generic operators.

eyegor2 years ago

C# does not allow generic custom operators, F# does, C doesn't, C++ does, several lisps do, but it's certainly not a common language feature. In C# you can overload most of the normal operators, but you can't, for example, define your own "~==" or overload "+=" to be anything other than "= self +"

lostmsu2 years ago

By generic operators I did not mean custom operator tokens, but operators, that take type parameters.

Or I did not understand the relevance of your comment to my pondering.