←back to thread

317 points est | 1 comments | | HN request time: 0.237s | source
Show context
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 #
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 #
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 #
ktpsns ◴[] No.17451770[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 #
1. hodgesrm ◴[] No.17451857[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.