<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div class="moz-text-flowed" style="font-family: -moz-fixed;
      font-size: 12px;" lang="x-unicode">On 08.09.19 09:53, Nathaniel
      Smith wrote:
      <br>
      <blockquote type="cite" style="color: #000000;">On Fri, Sep 6,
        2019 at 11:53 AM Ralf Gommers <a class="moz-txt-link-rfc2396E"
          href="mailto:ralf.gommers@gmail.com"><ralf.gommers@gmail.com></a>
        wrote:
        <br>
        <blockquote type="cite" style="color: #000000;">On Fri, Sep 6,
          2019 at 12:53 AM Nathaniel Smith <a
            class="moz-txt-link-rfc2396E" href="mailto:njs@pobox.com"><njs@pobox.com></a>
          wrote:
          <br>
          <blockquote type="cite" style="color: #000000;">On Tue, Sep 3,
            2019 at 2:04 AM Hameer Abbasi <a
              class="moz-txt-link-rfc2396E"
              href="mailto:einstein.edison@gmail.com"><einstein.edison@gmail.com></a>
            wrote:
            <br>
            <blockquote type="cite" style="color: #000000;">The fact
              that we're having to design more and more protocols for a
              lot
              <br>
              of very similar things is, to me, an indicator that we do
              have holistic
              <br>
              problems that ought to be solved by a single protocol.
              <br>
            </blockquote>
            But the reason we've had trouble designing these protocols
            is that
            <br>
            they're each different <span class="moz-smiley-s1"
              title=":-)"></span>. If it was just a matter of copying
            <br>
            __array_ufunc__ we'd have been done in a few minutes...
            <br>
          </blockquote>
          I don't think that argument is correct. That we now have two
          very similar protocols is simply a matter of history and
          limited developer time. NEP 18 discusses in several places
          that __array_ufunc__ should be brought in line with
          __array_ufunc__, and that we can migrate a function from one
          protocol to the other. There's no technical reason other than
          backwards compat and dev time why we couldn't use
          __array_function__ for ufuncs also.
          <br>
        </blockquote>
        Huh, that's interesting! Apparently we have a profoundly
        different
        <br>
        understanding of what we're doing here. To me, __array_ufunc__
        and
        <br>
        __array_function__ are completely different. In fact I'd say
        <br>
        __array_ufunc__ is a good idea and __array_function__ is a bad
        idea,
        <br>
        and would definitely not be in favor of combining them together.
        <br>
        <br>
        The key difference is that __array_ufunc__ allows for <b
          class="moz-txt-star"><span class="moz-txt-tag">*</span>generic<span
            class="moz-txt-tag">*</span></b>
        <br>
        implementations. Most duck array libraries can write a single
        <br>
        implementation of __array_ufunc__ that works for <b
          class="moz-txt-star"><span class="moz-txt-tag">*</span>all<span
            class="moz-txt-tag">*</span></b> ufuncs, even
        <br>
        new third-party ufuncs that the duck array library has never
        heard of,
        <br>
        because ufuncs all share the same structure of a loop wrapped
        around a
        <br>
        core operation, and they can treat the core operation as a black
        box.
        <br>
        For example:
        <br>
        <br>
        - Dask can split up the operation across its tiled sub-arrays,
        and
        <br>
        then for each tile it invokes the core operation.
        <br>
        - xarray can do its label-based axis matching, and then invoke
        the
        <br>
        core operation.
        <br>
        - bcolz can loop over the array uncompressing one block at a
        time,
        <br>
        invoking the core operation on each.
        <br>
        - sparse arrays can check the ufunc .identity attribute to find
        out
        <br>
        whether 0 is an identity, and if so invoke the operation
        directly on
        <br>
        the non-zero entries; otherwise, it can loop over the array and
        <br>
        densify it in blocks and invoke the core operation on each. (It
        would
        <br>
        be useful to have a bit more metadata on the ufunc, so e.g.
        <br>
        np.subtract could declare that zero is a right-identity but not
        a
        <br>
        left-identity, but that's a simple enough extension to make at
        some
        <br>
        point.)
        <br>
        <br>
        Result: __array_ufunc__ makes it totally possible to take a
        ufunc from
        <br>
        scipy.special or a random new on created with numba, and have it
        <br>
        immediately work on an xarray wrapped around dask wrapped around
        <br>
        bcolz, out-of-the-box. That's a clean, generic interface. [1]
        <br>
        <br>
        OTOH, __array_function__ doesn't allow this kind of
        simplification: if
        <br>
        we were using __array_function__ for ufuncs, every library would
        have
        <br>
        to special-case every individual ufunc, which leads to
        dramatically
        <br>
        more work and more potential for bugs.
        <br>
      </blockquote>
      <br>
      But uarray does allow this kind of simplification. <span
        class="moz-smiley-s1" title=":-)"></span> You would do the
      following inside a uarray backend:
      <br>
      <br>
      def __ua_function__(func, args, kwargs):
      <br>
          with ua.skip_backend(self_backend):
      <br>
              # Do code here, dispatches to everything but
      <br>
      <br>
      This is possible today and is done in the dask backend inside
      unumpy for example.
      <br>
      <br>
      <blockquote type="cite" style="color: #000000;">
        <br>
        To me, the whole point of interfaces is to reduce coupling. When
        you
        <br>
        have N interacting modules, it's unmaintainable if every change
        <br>
        requires considering every N! combination. From this
        perspective,
        <br>
        __array_function__ isn't good, but it is still somewhat
        constrained:
        <br>
        the result of each operation is still determined by the objects
        <br>
        involved, nothing else. In this regard, uarray even more extreme
        than
        <br>
        __array_function__, because arbitrary operations can be
        arbitrarily
        <br>
        changed by arbitrarily distant code. It sort of feels like the
        <br>
        argument for uarray is: well, designing maintainable interfaces
        is a
        <br>
        lot of work, so forget it, let's just make it easy to
        monkeypatch
        <br>
        everything and call it a day.
        <br>
        <br>
        That said, in my replies in this thread I've been trying to stay
        <br>
        productive and focus on narrower concrete issues. I'm pretty
        sure that
        <br>
        __array_function__ and uarray will turn out to be bad ideas and
        will
        <br>
        fail, but that's not a proven fact, it's just an informed guess.
        And
        <br>
        the road that I favor also has lots of risks and uncertainty. So
        I
        <br>
        don't have a problem with trying both as experiments and
        learning
        <br>
        more! But hopefully that explains why it's not at all obvious
        that
        <br>
        uarray solves the protocol design problems we've been talking
        about.
        <br>
        <br>
        -n
        <br>
        <br>
        [1] There are also some cases that __array_ufunc__ doesn't
        handle as
        <br>
        nicely. One obvious one is that GPU/TPU libraries still need to
        <br>
        special-case individual ufuncs. But that's not a limitation of
        <br>
        __array_ufunc__, it's a limitation of GPUs – they can't run CPU
        code,
        <br>
        so they can't use the CPU implementation of the core operations.
        <br>
        Another limitation is that __array_ufunc__ is weak at handling
        <br>
        operations that involve mixed libraries (e.g.
        np.add(bcolz_array,
        <br>
        sparse_array)) – to work well, this might require that bcolz
        have
        <br>
        special-case handling for sparse arrays, or vice-versa, so you
        still
        <br>
        potentially have some N**2 special cases, though at least here N
        is
        <br>
        the number of duck array libraries, not the number of ufuncs. I
        think
        <br>
        this is an interesting target for future work. But in general,
        <br>
        __array_ufunc__ goes a long way to taming the complexity of
        <br>
        interacting libraries and ufuncs.
        <br>
        <br>
        --
        <br>
        Nathaniel J. Smith -- <a class="moz-txt-link-freetext"
          href="https://vorpus.org">https://vorpus.org</a>
        <br>
        _______________________________________________
        <br>
        NumPy-Discussion mailing list
        <br>
        <a class="moz-txt-link-abbreviated"
          href="mailto:NumPy-Discussion@python.org">NumPy-Discussion@python.org</a>
        <br>
        <a class="moz-txt-link-freetext"
          href="https://mail.python.org/mailman/listinfo/numpy-discussion">https://mail.python.org/mailman/listinfo/numpy-discussion</a>
        <br>
      </blockquote>
      <br>
      <br>
    </div>
  </body>
</html>