←back to thread

317 points est | 10 comments | | HN request time: 0.973s | source | bottom
1. jkabrg ◴[] No.17450249[source]
It would be nice if in Python you could define new operators instead of overloading existing ones. It would make matrix multiplication look nicer.

I'm thinking it could look like this:

  import numpy as np
  
  def M1 %*% M2 with same precedence as *:
    return M1.matmul(M2)

  foo_matrix = np.matrix([[1,1],[1,1]])
  bar_matrix = np.matrix([[2,2],[2,2]])
  print(foo_matrix %*% bar_matrix)
Also, it would be nice to have a pipe operator `%>%` such that

  foo %>% f()
is equivalent to

  f(foo)
The alternative is to make f a method of foo, and then you can write

  foo.f()
But what happens if I don't want f to be a method? I just want the style of writing the f after the foo, but I don't want the baggage of OOP. Is that anti-Pythonic?
replies(6): >>17450465 #>>17450468 #>>17450486 #>>17451059 #>>17451069 #>>17452494 #
2. misnome ◴[] No.17450465[source]
I know your question isn't specifically about the exact example, but are you aware that they added the @ operator for matmul in 3.5?

I think anything general would probably be considered as cluttering up the grammar or non-pythonic. Completely custom unicode operators was something I loved about Swift (open to abuse, but really useful in moderation).

3. Franciscouzo ◴[] No.17450468[source]
Maybe you already know, but the @ operator already exists for matrix multiplication.
4. ben509 ◴[] No.17450486[source]
I think the problem with custom operators is they have custom associativity and precedence. Not saying this is a deal breaker, just the biggest issue you'll run into.

In toy examples, you can see all that because they'll show the declarations.

Here's a case[1] trying to explain monads by showing a simpler example:

    parseP5_take2 s =
     matchHeader (L8.pack "P5") s       >>?
      \s -> skipSpace ((), s)           >>?
      (getNat . snd)                    >>?
      skipSpace                         >>?
      \(width, s) ->   getNat s         >>?
      skipSpace                         >>?
      \(height, s) ->  getNat s         >>?
      \(maxGrey, s) -> getBytes 1 s     >>?
      (getBytes (width * height) . snd) >>?
      \(bitmap, s) -> Just (Greymap width height maxGrey bitmap, s)
Is all that left or right associative?

Generally, to read an expression when you have custom operators, you have to dig up the associativity and precedence. That's why I see many newer libraries avoiding custom operators, [2] vs [3].

I think you can do custom operators in a language, you just need to require that the associativity and precedence be declared in import. I'd also have named levels of associativity, so maybe:

    import MyModule.Foo.Bar (op(!$!, additive, right), ...)
Now, that's some boilerplate in your imports, but for readability, it'd be huge. And automated tools can tweak stuff like this.

[1] http://book.realworldhaskell.org/read/monads.html

[2] http://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-...

[3] https://hackage.haskell.org/package/prettyprinter-1.2.1/docs...

replies(1): >>17451243 #
5. ASalazarMX ◴[] No.17451059[source]
Why not a repipe gluing operator so that

    foo%$%bar %>%>% f()
is equivalent to

    (f(foo(bar)), f(bar(foo)))
I'm just kidding, but please no more cryptic symbols in Python's syntax, if it can be solved with functions instead. We have other languages to scratch that itch.

I haven't read the PEP, so I don't know the trade-offs, but I'm not loving this (:=) syntactic sugar either.

6. ericfrederich ◴[] No.17451069[source]
Check this one out. https://github.com/borntyping/python-infix
7. hodgesrm ◴[] No.17451243[source]
On my first C++ project we enthusiastically implemented customized versions of operators like '+' on new types. It turned out to be really confusing for exactly the reasons you mention.

Operator overloading in general only seems to be practical for mathematical types like sets or sequences where the rules are well-defined thanks to generations of people thinking about them. Yet, even the set case works poorly for C++ because in addition to associativity new operators also inherit the built-in operator precedence rules. For example should * take precedence over + in set operations? (Assuming you implement * for cartesian product and + for union.)

Maybe C++ has changed since I used it but this sort of thing really gets in the way of writing correct code.

replies(1): >>17451770 #
8. ktpsns ◴[] No.17451770{3}[source]
From my feeling, C++ makes it verbose to overload operators in every possible context. Thinking of Foo::operator()+, there are all these border cases where custom overload functions have to be provided (such as "(int) + (Foo)", "(Foo) + (int)", "... += Foo", etc.). I assume that in other languages it is probably simpler to fully implement own operators.
replies(1): >>17451857 #
9. hodgesrm ◴[] No.17451857{4}[source]
Exactly. This part of C++ always felt experimental to me--something that was plausible to try but ended up digging a hole with a lot more complexity than the language designers perhaps intended.
10. joshuamorton ◴[] No.17452494[source]
>Also, it would be nice to have a pipe operator `%>%` such that

You can absolutely define a pipe operator to apply functions. Something like

    class Pipeable(object):
        def __init__(self, f: Callable):
            self.f = f

        def __or__(self, other: Callable):
            return Pipeable(other(self.f))
Then use it like

    composed = Pipeable(foo) | f
Replace `|` with `>>` if you feel that conveys what you want better. Libraries already exist which provide this interface.

>Is that anti-Pythonic?

In a sense, yes. Operators and methods are defined in terms of objects, not in terms of abstract types.