I followed the compiler's "suggestions" and I think I came to an dead end. It's stuck with lazy val optimizedSelf = optimize(this)._2 - if I try to add implicit values, it complains about Ordering[Any] + some of the covariant type parameters being used in invariant positions:

```scala
  sealed abstract class Matrix2[+R,+C,V] {
    val sizeHint: SizeHint = NoClue
    def +(that: Matrix2[R,C,V])(implicit mon: Monoid[V], ord: Ordering[(R,C)]): Matrix2[R,C,V] = Sum(this, that, mon, ord)
    def *[C2](that: Matrix2[C,C2,V])(implicit ring: Ring[V], ord: Ordering[C], ord2: Ordering[(R,C2)]): Matrix2[R,C2,V] = Product(this, that, false, ring, ord, ord2)
    val tpipe: TypedPipe[(R, C, V)]
    def transpose: Matrix2[C,R,V] = Literal(tpipe.map(x => (x._2, x._1, x._3)), sizeHint)

    lazy val optimizedSelf = optimize(this)._2
  }

  case class Product[R,C,C2,V](left: Matrix2[R,C,V], right: Matrix2[C,C2,V], optimal: Boolean = false, ring: Ring[V], ord: Ordering[C], ord2: Ordering[(R,C2)]) extends Matrix2[R,C2,V] {
    def toPipe(): TypedPipe[(R, C2, V)] = {
      if (optimal) {
        // TODO: pick the best joining algorithm based the sizeHint
        val one = left.tpipe.groupBy(x => x._2)(ord)
        val two = right.tpipe.groupBy(x => x._1)(ord)

        one.join(two).mapValues { case (l, r) => (l._1, r._2, ring.times(l._3, r._3)) }.values.
          groupBy(w => (w._1, w._2))(ord2).mapValues { _._3 }
          .sum(ring)
          .filter { kv => ring.isNonZero(kv._2) }
          .map { case ((r, c), v) => (r, c, v) }

      } else {
        optimizedSelf.tpipe
      }
    }

    override lazy val tpipe = toPipe()
    override val sizeHint = left.sizeHint * right.sizeHint
  }

  case class Sum[R, C, V](left: Matrix2[R, C, V], right: Matrix2[R, C, V], mon: Monoid[V], ord: Ordering[(R,C)]) extends Matrix2[R, C, V] {
    def toPipe(): TypedPipe[(R, C, V)] = {
      if (left.equals(right)) {
        left.optimizedSelf.tpipe.map(v => (v._1, v._2, mon.plus(v._3, v._3)))
      } else {
        (left.optimizedSelf.tpipe ++ right.optimizedSelf.tpipe).groupBy(x => (x._1, x._2))(ord).mapValues { _._3 }
          .sum(mon)
          .filter { kv => mon.isNonZero(kv._2) }
          .map { case ((r, c), v) => (r, c, v) }
      }
    }

    override lazy val tpipe = toPipe()
    override val sizeHint = left.sizeHint + right.sizeHint
  }

  case class Literal[R,C,V](override val tpipe: TypedPipe[(R, C, V)], override val sizeHint: SizeHint) extends Matrix2[R, C, V]

  /**
   * The original prototype that employs the standard O(n^3) dynamic programming
   * procedure to optimize a matrix chain factorization
   */
  def optimizeProductChain[R,C,V](p: IndexedSeq[Literal[R,C,V]])(implicit ring: Ring[V], ord1: Ordering[Any], ord2: Ordering[(R,C)]): (Long, Matrix2[R,C,V]) = {

    val subchainCosts = HashMap.empty[(Int, Int), Long]

    val splitMarkers = HashMap.empty[(Int, Int), Int]

    def computeCosts(p: IndexedSeq[Literal[R,C,V]], i: Int, j: Int): Long = {
      if (subchainCosts.contains((i, j))) subchainCosts((i, j))
      if (i == j) subchainCosts.put((i, j), 0)
      else {
        subchainCosts.put((i, j), Long.MaxValue)
        for (k <- i to (j - 1)) {
          val cost = computeCosts(p, i, k) + computeCosts(p, k + 1, j) +
            (p(i).sizeHint * (p(k).sizeHint * p(j).sizeHint)).total.getOrElse(0L)
          if (cost < subchainCosts((i, j))) {
            subchainCosts.put((i, j), cost)
            splitMarkers.put((i, j), k)
          }
        }
      }

      subchainCosts((i, j))
    }

    def generatePlan(i: Int, j: Int): Matrix2[R,C,V] = {
      if (i == j) p(i)
      else {
        val k = splitMarkers((i, j))
        val left = generatePlan(i, k)
        val right = generatePlan(k + 1, j)
        val result = Product(left, right, true, ring, ord1, ord2)
        result
      }

    }

    val best = computeCosts(p, 0, p.length - 1)

    (best, generatePlan(0, p.length - 1))
  }

  /**
   * This function walks the input tree, finds basic blocks to optimize,
   * i.e. matrix product chains that are not interrupted by summations.
   * One example:
   * A*B*C*(D+E)*(F*G) => "basic blocks" are ABC, D, E, and FG
   *
   * TODO: "global" optimization - i.e. over optimize over basic blocks. In the above example, we'd treat (D+E) as a temporary matrix T and optimize the whole chain ABCTFG
   * TODO: make use of distributivity to generate more variants. In the above example, we could also generate ABCDFG + ABCEFG and have basic blocks: ABCDFG, and ABCEFG
   */
  def optimize[R,C,V](mf: Matrix2[R,C,V])(implicit ring: Ring[V], ord1: Ordering[Any], ord2: Ordering[(R,C)]): (Long, Matrix2[R,C,V]) = {

    /**
     * Helper function that either returns an optimized product chain
     * or the last visited place in the tree
     */
    def chainOrLast(chain: List[Literal[R,C,V]], last: Option[(Long, Matrix2[R,C,V])]): (Long, Matrix2[R,C,V]) = {
      if (chain.isEmpty) last.get
      else optimizeProductChain(chain.toIndexedSeq)
    }

    /**
     * Recursive function - returns a flatten product chain so far and the rest of the connected tree
     */
    def optimizeBasicBlocks(mf: Matrix2[R,C,V]): (List[Literal[R,C,V]], Option[(Long, Matrix2[R,C,V])]) = {
      mf match {
        // basic block of one matrix
        case element: Literal[R,C,V] => (List(element), None)
        // two potential basic blocks connected by a sum
        case Sum(left, right, _, _) => {
          val (lastLChain, leftTemp) = optimizeBasicBlocks(left)
          val (lastRChain, rightTemp) = optimizeBasicBlocks(right)
          val (cost1, newLeft) = chainOrLast(lastLChain, leftTemp)
          val (cost2, newRight) = chainOrLast(lastRChain, rightTemp)
          (Nil, Some(cost1 + cost2, Sum(newLeft, newRight, ring, ord2)))
        }
        // basic block A*B
        case Product(leftp: Literal[R,C,V], rightp: Literal[R,C,V], _, _, _, _) => {
          (List(leftp, rightp), None)
        }
        // potential chain (...something...)*right or just two basic blocks connected by a product
        case Product(left: Product[R,C,C,V], right: Literal[R,C,V], _, _, _, _) => {
          val (lastLChain, leftTemp) = optimizeBasicBlocks(left)
          if (lastLChain.isEmpty) {
            val (cost, newLeft) = leftTemp.get
            val interProduct = Product(newLeft, right, true, ring, ord1, ord2)
            (Nil, Some(cost, interProduct))
          } else {
            (lastLChain ++ List(right), leftTemp)
          }
        }
        // potential chain left*(...something...) or just two basic blocks connected by a product
        case Product(left: Literal[R,C,V], right: Product[R,C,C,V], _, _, _, _) => {
          val (lastRChain, rightTemp) = optimizeBasicBlocks(right)
          if (lastRChain.isEmpty) {
            val (cost, newRight) = rightTemp.get
            val interProduct = Product(left, newRight, true, ring, ord1, ord2)
            (Nil, Some(cost, interProduct))
          } else {
            (left :: lastRChain, rightTemp)
          }
        }
        // potential chain (...something...)*(...something...) or just two basic blocks connected by a product
        case Product(left: Matrix2[R,C,V], right: Matrix2[R,C,V], _, _, _, _) => {
          val (lastLChain, leftTemp) = optimizeBasicBlocks(left)
          val (lastRChain, rightTemp) = optimizeBasicBlocks(right)
          if (lastLChain.isEmpty) {
            val (cost1, newLeft) = leftTemp.get
            val (cost2, newRight) = chainOrLast(lastRChain, rightTemp)
            (Nil, Some(cost1 + cost2, Product(newLeft, newRight, true, ring, ord1, ord2)))
          } else {
            if (lastRChain.isEmpty) {
              val (cost1, newLeft) = optimizeProductChain(lastLChain.toIndexedSeq)
              val (cost2, newRight) = rightTemp.get
              (Nil, Some(cost1 + cost2, Product(newLeft, newRight, true, ring, ord1, ord2)))
            } else {
              (lastLChain ++ lastRChain, None)
            }
          }
        }
      }
    }
    val (lastChain, form) = optimizeBasicBlocks(mf)

    chainOrLast(lastChain, form)
  }