Allowing parameters in-between function names

As we all know, python as a programming language is known to have an elegant code and an ability to express intent very clearly in a few lines of code that look even like a prose.

However, there are situations when the names of function does not express the intent, and there is need of additional documentation when a simple name should suffice for most cases.

The situation is like follow:

def contains_subset_(this_set, set_list):
    return any(a_set.issubset(this_set) for a_set in set_list)

Instead of this function, if it were contains_substring(str1, str2), both parameters would be string, and it would be confusing for users to know which is supposed to be the substring, str1 or str2.

I would like to suggest a feature that would fix this issue, enhance code readability, and improve ability to use the code without having to perform mental conversion to keep track of where should each parameter go.

I would like a support for codes written in the following way:

def check_if_(this_set)_has_subset_in_(list_of_sets):
    return any(a_set.issubset(this_set) for a_set in list_of_sets)

my_list_of_sets = [{1,2}, {1,3}, {2,3}]
check_if_({1,2,4})_has_subset_in_(my_list_of_sets) # true

I believe this can be done very easily and automatically by a parser in the following way:

  1. def check_if_(this_set)_has_subset_in_(list_of_sets): becomes def check_if__has_subset_in_(this_set, list_of_sets):
  2. check_if_({1,2,4})_has_subset_in_(my_list_of_sets) becomes check_if__has_subset_in_({1,2,4}, my_list_of_sets)

i.e. when parser sees a function called in following way, it finds the complete function name has ended by keeping track of delimiters such as ' ' (space) or '.' (dot) or similar meanwhile, keeping track of the parameters passed midway through the name. Then the name parts of function are connected together and all parameters are bubbled all the way to the end.

It would be so nice to see this implemented in python.

You can actually already do this! Albeit in a somewhat weird way.

You can define brand new infix operators (that’s where the operator is between the two things it works on, like how the + operator sits between two things you want to add, eg 3 + 4) using functions, simply by decorating them.

This can be currently done with something like:

class check_if:
    def __init__(self, this_set):
        self.this_set = this_set

    def has_subset_in(self, list_of_sets):
        return any(a_set.issubset(self.this_set) for a_set in list_of_sets)

assert check_if({1, 2, 4}).has_subset_in(my_list_of_sets)
3 Likes

Oh, this is indeed possible in python in a way. I presume this would come with small runtime overhead of creating a class instance. If parameters in-between function names were actually allowed, this approach would be just as efficient as normal function calling.

Just as prior art, Agda supports something similar with mixfix operators.

Your proposed function

def check_if_(this_set)_has_subset_in_(list_of_sets):
    return any(a_set.issubset(this_set) for a_set in list_of_sets)

would look something like

-- what, no syntax coloring for Agda?? :)

checkIf_hasSubsetIn_ : Container → List Container → Bool
checkIf thisSet hasSubSetIn setList = any (\x -> issubset x thisSet) setList

Each underscore in the operator name represents an unnamed parameter, with
argument names thisSet and setList supplied in the function definition. hasSubsetIn looks like an argument, but it’s really part of the operator, just like if and then in Python’s ... if ... then ....

(In fact, Agda’s conditional expression is just such a mixfix operator:

if_then_else_ : {A : Set} → Bool → A → A → A
if true then x else y = x
if false then x else y = y

)

(Set is Agda’s keyword for types, so I made up Container to represent a type similar to Python’s set, and assume that any or issubset have obvious implementations. I’m not really that familiar with Agda; this idea just reminded me of this particular Agda feature.)

1 Like

The usual way to get to this goal in python is to use keyword arguments, and this can be enforced with keyword-only arguments.

E.g.:


import datetime

datetime.date(2024, 8, 1)   # Is this the 1st of August or the 8th of januari?
datetime.date(year=2024, month=8, day=1)

The idea is cool, though I find the use of two arbitrarily-chosen operators around an “infix function” to be aesthetically weird indeed.

I would reimplement the idea by sticking to a specific operator such as @ (because it’s rarely used) for the left operand and use the call syntax for the right operand to make it look a little less weird:

def infix(func):
    class Infix:
        def __init__(self, right_op):
            self.right_op = right_op
        def __rmatmul__(self, left_op):
            return func(left_op, self.right_op)
    return Infix

@infix
def has_subset_in(this_set, list_of_sets):
    return any(a_set.issubset(this_set) for a_set in list_of_sets)

assert {1, 2, 4}@has_subset_in([{1, 2}, {1, 3}, {2, 3}]) # True

Sure, you can do it that way. As you’ve shown, you have full flexibility for this.