Infix function in python

Kotlin has a beautiful concept of infix functions. It would be greate if python introduces such feature with infix tag. you can learn about infix functions here.

It would be great if we could do the below thing in python

walletBalance = 0

infix def addMoney(amount):
       global walletBalance
       walletBalance += amount

infix def deduct(amount):
      global walletBalance
      walletBalance -= amount

walletBalance addMoney 1000
walletBalance deduct 50
2 Likes

Hello,
If you need that kind of feature you can do it by tricking some existing operators to have something that looks like a custom operator.

For example:

class Infix:
    def __init__(self, func, lhs=None):
        self.func = func
        self.lhs = lhs

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

    def __ror__(self, lhs):
        return __class__(self.func, lhs)

    def __or__(self, rhs):
        return self(self.lhs, rhs)


add = Infix(lambda a, b: a+b)
print(4 |add| 5)

You have also some existing libraries (https://pypi.org/project/infix/) that implement this pattern.

6 Likes

How is that better than addMoney(1000) or walletBalance += 1000? It just seems worse.

5 Likes

What does a b c d e mean?

d(b(a,c),e), b(a,d(c,e)) or c(b,d)(a,e)?

5 Likes

No, i didn’t mean to use it like this. I just want to create own custom operators. I know that it wont’ be used this way but for easier understanding I showcased this simple example

would be great if python provide such feature on its own just like kotlin. My first language is Kotlin as i am an android developer so i suggested a feature here because the python repo suggested me to share feature suggestion here

You might want to also show examples where it is beneficial.

You could do the Haskell thing where a binary function can ve used as a binary operator by using backticks, so translated to Python it could be done like this

def foo(a, b): ...

c = 1 `foo` 2

which is unambiguous.

But Haskell is a very different beast from Python so I don’t think it’s a good fit here.

2 Likes

If they are beneficial in Kotlin they will prove to be helpful in python too.

Infix functions can be beneficial in several ways:

Readability: Infix functions can make your code look much more like a natural language. This can improve the readability of your code, especially when the function name stands between the arguments.

Chainability: One of the significant advantages of infix functions is the ability to chain multiple function calls together, resulting in more fluent and readable code.

Domain-Specific Languages (DSLs): Infix functions can be especially useful when writing a Domain Specific Language for your application, allowing the DSL code to be much more readable1.

Intuitiveness: Infix notation is very intuitive and easy to read. Most people are familiar with such notation from their school days, so it comes naturally to them4.

Here’s an example of an infix function in Kotlin

class Assertion<T>(private val target: T) {
    infix fun isEqualTo(other: T) {
        Assert.assertEquals(other, target)
    }

    infix fun isDifferentFrom(other: T) {
        Assert.assertNotEquals(other, target)
    }
}

In this example, isEqualTo and isDifferentFrom are infix functions. They can be used in the following way:

val assertion = Assertion(5)
assertion isEqualTo 5
assertion isDifferentFrom 6

This results in code that reads nicely from left to right.

In Kotlin, infix functions provide a way to call functions with a more natural, DSL-like syntax. They are beneficial in various scenarios, making the code more concise and readable. Here are some situations where infix functions are particularly useful:

  1. DSL (Domain-Specific Language) Creation:
    Infix functions are commonly used in DSLs to create a fluent and readable syntax. DSLs are often used for configuring objects or expressing operations in a specific domain. Infix functions help in achieving a more natural language-like syntax.

    class Configuration {
        infix fun set(key: String, value: Any) {
            // Set configuration key-value pair
        }
    }
    
    val config = Configuration()
    config set "color" to "blue"   // Using infix function for DSL-like syntax
    
  2. Mathematical Expressions:
    Infix functions can be beneficial for creating mathematical expressions in a more readable form.

    infix fun Int.add(x: Int): Int {
        return this + x
    }
    
    val result = 5 add 3   // Using infix function for a more natural expression
    
  3. Collection Operations:
    Infix functions are commonly used with collection operations to enhance readability. For example, Kotlin’s standard library has infix functions like to for creating pairs.

    val pair = "key" to "value"   // Using infix function for creating pairs
    
  4. Assertion Functions:
    Infix functions are often used with assertion functions to create more readable assertions.

    infix fun String.shouldBeEqualTo(other: String) {
        // Assertion logic
    }
    
    "hello" shouldBeEqualTo "hello"   // Using infix function for assertion
    
  5. Custom DSLs for Testing:
    In testing scenarios, infix functions can be used to create more readable test assertions.

    infix fun <T> T.shouldEqual(expected: T) {
        // Assertion logic
    }
    
    val result = performOperation()
    result shouldEqual expectedValue   // Using infix function for test assertion
    

In summary, infix functions in Kotlin are beneficial in scenarios where a more natural, readable, and domain-specific syntax is desired, such as DSLs, mathematical expressions, collection operations, assertions, and custom test DSLs. They contribute to Kotlin’s goal of providing a concise and expressive language.
same things are also needed in python. so I requested this feature.

“Make Python look like this other language” and “if it works in that language then it will be good in Python” aren’t particularly compelling reasons for change. The obvious answer if you really need/like that syntax and can’t do it the Python way is “do it with Kotlin if you like Kotlin”. What you’re proposing is just different, not better. You need to demonstrate that it’s better than how you would currently do this in Python.

12 Likes

Side note: That argument is backwards. First show how they’re useful in Python, and then back that up by saying “they have proven their worth in Kotlin” as a demonstration of prior art. Notably, it’s important to show that they fit well into the Python ecosystem, which (by definition) isn’t the same as Kotlin’s, so the mere fact that they work in other languages isn’t enough on its own.

This is definitely true for common tasks. And the notion of “common” does vary from context to context, which is why - after many years of asking - the numeric computational folks got a new operator for matrix multiplication.

Yes, but it isn’t the only way to chain. Method chaining is also very popular in many languages - doing things ilke:

thing.color("red").shape("square").position(100, 100)

It isn’t common in Python. (Where it DOES exist, it’s usually because there’s some external API that’s being mirrored.) Do you know why this is? Can you address the reason that method chaining is unpopular, and show that infix chaining is still worthwhile?

It’s been stated a number of times that making Python better able to be a DSL is not a goal. To be quite honest, I have had a LOT of fun actually designing DSLs for various applications, and I think it’s a good exercise for people to do; but also, it would give a different appreciation for certain tools. It might be a better choice to design a true DSL for your configuration files, and then have a Python script that parses and executes it, rather than making your config files have the entire power of the Python interpreter. You might end up with something like:

# Configuration
set "color" to "blue"

which doesn’t really work in Python, but would be absolutely fine if you’re designing a language from scratch. (Ah. No, I don’t mean designing a language from Scratch, although that IS a great start.)

Want a place to start playing with DSLs? Mathematics is WAY more complicated than Python’s own expression evaluator. Try designing a language that better expresses some of the notations used by mathematicians, freed from the requirement that it be valid Python. Design a parser and evaluator for that language. How powerful can you make it?

(Tip: Look for an LALR parser library - there are a few of them on PyPI.)

This is definitely true, but only for the parts that were actually learned in school. For fundamental operations of arithmetic, that’s an incredibly strong argument! But Python’s already doing those with infix operators. Outside of that, there are only a handful of “operators” that are well enough known to justify this; for example, among tabletop gamers, the notation 2d6 is probably sufficiently established that you could treat the letter d as an infix operator. [1]

There’s one fairly major consideration that hasn’t been addressed here, though, and that’s the precedence and associativity of new operators. When you see an expression like x * (y + z * q) / w + e * r you can figure out what the order of operations is by knowing which operators bind more tightly than others. For operators to be intuitive, this HAS to be done correctly - imagine if a programming language had multiplication happen before addition, but division after. When you design these new operators, where do they sit? How do you make sure they’re going to behave correctly?

Again, this is nothing against the general idea of making new operators, and again, if you’re creating an actual DSL with its own grammar, you won’t have a problem here, but a simple “infix” keyword on a function does not solve this. The only way it would work is for all infix operators to have the same precedence and associativity, and that’s extremely limiting. (I suppose you could have multiple “infix” keywords, but at that point it’s getting extremely messy.)

There ARE some Python packages that create infix operators (at least one has already been mentioned here). They use a neat trick to define precedence: they are built on existing infix operators, so you get to choose which operator to use. That helps a lot, but you’re still limited to the available operators.

So… of your four use cases:

  1. DSL creation: Do it properly, make an actual DSL. It’s more work to get started than just “it’s Python but I added this operator”, but ultimately, you’ll probably benefit from the flexibility. Plus, it’s fun!
  2. Mathematical expressions: These are extremely sensitive to precedence rules, so they’re going to struggle the most with arbitrary rules like “all custom infix operators are at the same precedence”. You lose a lot of the benefit if you don’t have that power.
  3. Collection operations: Honestly, Python’s {key: value} syntax seems fine to me, but maybe that’s just because I’m accustomed to it from multiple languages. I can’t see a lot of value in making a custom syntax for this - if I were to make a custom collection, I’d initialize it using either a dictionary or kwargs. But maybe a better use-case would convince me??
  4. Assertions. These generally work fine with method chaining. In JavaScript projects, it’s very common to use something like this (borrowing from Mocha): expect(x).to.equal(5) (it allows “fluff” methods like “to” with no semantics, to allow English-like readability). In terms of readability, this is pretty much on par with what you have, and doesn’t require any new syntax.
  5. Custom DSLs for testing. Not sure how this is any different from assertions, given that test frameworks basically work by doing stuff and then asserting truths about the result. I’d consider these to be the same use case.

So… custom infix functions tend to be one of those things that always looks tempting, but never really has that strong a position. Sure, they might be useful, but the devil’s in the details, and the actual use-cases end up being as well served by other syntaxes.


  1. It means “roll two dice, each of which has six sides, and take the sum of the results”. Dungeons and Dragons. ↩︎

5 Likes

Without allowing functions to be called things like “∨”, “∩” it is rather useless as a feature I think.

1 Like

I’m not sure it’s really beneficial. Anyway, I think this is a good real world example:

infix def xor(a, b):
    return bool(a) != bool(b)
1 Like

Mh, maybe I didn’t understood the question, because IMHO a b c d e means d(b(a,c),e), like 1 + 2 + 3 means add(add(1, 2), 3). But maybe I’m missing something more subtle?

You’re missing the fact that 1 + 2 * 3 means add(1, mul(2, 3)). There’s no reason that in a b c d e b and d must be the same operator, or that they have the same precedence, or even that they associate to the left - 1 ** 2 ** 3 is pow(1, pow(2, 3)), which is different from your example using addition.

This isn’t actually particularly subtle - it’s the fundamental problem that makes infix operators different from normal functions.

1 Like

In the sintax proposed by the author, there’s no way to specify a precedence “power”, so we can suppose all the custom operators will have the same precedence, @bzox?

1 Like

You’re right, but can’t we simply choose a default for custom operators?

1 Like

That’s the exact problem, though. This single precedence is going to be wrong for a lot of those operators. This is no minor problem - it’s a fundamentally hard problem and a major barrier to readability.

4 Likes

Maybe you’re right… and there’s another problem I didn’t think before. If in a future Python wants to introduce a new operator that was already defined as infix in a lot of code, it will be a nightmare.

1 Like

[Rosuav] Chris Angelico https://discuss.python.org/u/rosuav Rosuav

This single precedence is going to be
wrong for a lot of those operators.

A long time ago I devised a language that had a very minimal core,
with everything else being user defined, including binary operators.

I sidestepped the precedence problem by requiring all expressions
to be parenthesesed. You couldn’t write a+b+c, it had to be either
((a+b)+c) or (a+(b+c)).

Not claiming this is a good idea, just something to consider.

1 Like