diff --git a/grudge/reductions.py b/grudge/reductions.py
index 5337349e1c1701b6389292ffdcde67f47b9a72eb..d9b0a6bad37a84bd33f4482dea8d6ba939e5c40f 100644
--- a/grudge/reductions.py
+++ b/grudge/reductions.py
@@ -60,10 +60,7 @@ THE SOFTWARE.
 from numbers import Number
 from functools import reduce
 
-from arraycontext import (
-    ArrayContext,
-    make_loopy_program
-)
+from arraycontext import make_loopy_program
 
 from grudge.discretization import DiscretizationCollection
 
@@ -260,36 +257,15 @@ def integral(dcoll: DiscretizationCollection, dd, vec) -> float:
 
 # {{{  Elementwise reductions
 
-def _map_elementwise_reduction(actx: ArrayContext, op_name):
-    @memoize_in(actx, (_map_elementwise_reduction,
-                       "elementwise_%s_prg" % op_name))
-    def prg():
-        return make_loopy_program(
-            [
-                "{[iel]: 0 <= iel < nelements}",
-                "{[idof, jdof]: 0 <= idof, jdof < ndofs}"
-            ],
-            """
-                result[iel, idof] = %s(jdof, operand[iel, jdof])
-            """ % op_name,
-            name="grudge_elementwise_%s_knl" % op_name
-        )
-    return prg()
-
-
-def elementwise_sum(dcoll: DiscretizationCollection, *args) -> DOFArray:
+def _apply_elementwise_reduction(
+        op_name: str, dcoll: DiscretizationCollection, *args) -> DOFArray:
     r"""Returns a vector of DOFs with all entries on each element set
-    to the sum of DOFs on that element.
+    to the reduction operation *op_name* over all degrees of freedom.
 
-    May be called with ``(dcoll, vec)`` or ``(dcoll, dd, vec)``.
-
-    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
-        Defaults to the base volume discretization if not provided.
-    :arg vec: a :class:`~meshmode.dof_array.DOFArray`
-    :returns: a :class:`~meshmode.dof_array.DOFArray` whose entries
-        denote the element-wise sum of *vec*.
+    :arg \*args: Arguments for the reduction operator, such as *dd* and *vec*.
+    :returns: a :class:`~meshmode.dof_array.DOFArray` or object arrary of
+        :class:`~meshmode.dof_array.DOFArray`s.
     """
-
     if len(args) == 1:
         vec, = args
         dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE)
@@ -302,64 +278,63 @@ def elementwise_sum(dcoll: DiscretizationCollection, *args) -> DOFArray:
 
     if isinstance(vec, np.ndarray):
         return obj_array_vectorize(
-            lambda vi: elementwise_sum(dcoll, dd, vi), vec
+            lambda vi: _apply_elementwise_reduction(op_name, dcoll, dd, vi), vec
         )
 
     actx = vec.array_context
 
+    @memoize_in(actx, (_apply_elementwise_reduction,
+                       "elementwise_%s_prg" % op_name))
+    def elementwise_prg():
+        return make_loopy_program(
+            [
+                "{[iel]: 0 <= iel < nelements}",
+                "{[idof, jdof]: 0 <= idof, jdof < ndofs}"
+            ],
+            """
+                result[iel, idof] = %s(jdof, operand[iel, jdof])
+            """ % op_name,
+            name="grudge_elementwise_%s_knl" % op_name
+        )
+
     return DOFArray(
         actx,
         data=tuple(
-            actx.call_loopy(
-                _map_elementwise_reduction(actx, "sum"),
-                operand=vec_i
-            )["result"]
+            actx.call_loopy(elementwise_prg(), operand=vec_i)["result"]
             for vec_i in vec
         )
     )
 
 
-def elementwise_max(dcoll: DiscretizationCollection, *args) -> DOFArray:
+def elementwise_sum(dcoll: DiscretizationCollection, *args) -> DOFArray:
     r"""Returns a vector of DOFs with all entries on each element set
-    to the maximum over all DOFs on that element.
+    to the sum of DOFs on that element.
 
     May be called with ``(dcoll, vec)`` or ``(dcoll, dd, vec)``.
 
-    :arg dcoll: a :class:`grudge.discretization.DiscretizationCollection`.
     :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
         Defaults to the base volume discretization if not provided.
     :arg vec: a :class:`~meshmode.dof_array.DOFArray`
     :returns: a :class:`~meshmode.dof_array.DOFArray` whose entries
-        denote the element-wise max of *vec*.
+        denote the element-wise sum of *vec*.
     """
+    return _apply_elementwise_reduction("sum", dcoll, *args)
 
-    if len(args) == 1:
-        vec, = args
-        dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE)
-    elif len(args) == 2:
-        dd, vec = args
-    else:
-        raise TypeError("invalid number of arguments")
-
-    dd = dof_desc.as_dofdesc(dd)
 
-    if isinstance(vec, np.ndarray):
-        return obj_array_vectorize(
-            lambda vi: elementwise_max(dcoll, dd, vi), vec
-        )
+def elementwise_max(dcoll: DiscretizationCollection, *args) -> DOFArray:
+    r"""Returns a vector of DOFs with all entries on each element set
+    to the maximum over all DOFs on that element.
 
-    actx = vec.array_context
+    May be called with ``(dcoll, vec)`` or ``(dcoll, dd, vec)``.
 
-    return DOFArray(
-        actx,
-        tuple(
-            actx.call_loopy(
-                _map_elementwise_reduction(actx, "max"),
-                operand=vec_i
-            )["result"]
-            for vec_i in vec
-        )
-    )
+    :arg dcoll: a :class:`grudge.discretization.DiscretizationCollection`.
+    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
+        Defaults to the base volume discretization if not provided.
+    :arg vec: a :class:`~meshmode.dof_array.DOFArray`
+    :returns: a :class:`~meshmode.dof_array.DOFArray` whose entries
+        denote the element-wise max of *vec*.
+    """
+    return _apply_elementwise_reduction("max", dcoll, *args)
 
 
 def elementwise_min(dcoll: DiscretizationCollection, *args) -> DOFArray:
@@ -375,34 +350,7 @@ def elementwise_min(dcoll: DiscretizationCollection, *args) -> DOFArray:
     :returns: a :class:`~meshmode.dof_array.DOFArray` whose entries
         denote the element-wise min of *vec*.
     """
-
-    if len(args) == 1:
-        vec, = args
-        dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE)
-    elif len(args) == 2:
-        dd, vec = args
-    else:
-        raise TypeError("invalid number of arguments")
-
-    dd = dof_desc.as_dofdesc(dd)
-
-    if isinstance(vec, np.ndarray):
-        return obj_array_vectorize(
-            lambda vi: elementwise_min(dcoll, dd, vi), vec
-        )
-
-    actx = vec.array_context
-
-    return DOFArray(
-        actx,
-        tuple(
-            actx.call_loopy(
-                _map_elementwise_reduction(actx, "min"),
-                operand=vec_i
-            )["result"]
-            for vec_i in vec
-        )
-    )
+    return _apply_elementwise_reduction("min", dcoll, *args)
 
 
 def elementwise_integral(dcoll: DiscretizationCollection, dd, vec) -> DOFArray: