Well, this comment will respond to a couple of the above.

First, I question how efficient we need to make addTrap. Isn't this technically an anti pattern? That is to say, if you're swallowing errors, something is going wrong. This is the intention in the cascading docs, at least.

That said, let's take a look at your suggestion above.

    def addTrap(sink: Source with TypedSink[T])(implicit fd: FlowDef, m: Mode): TypedPipe[T] = {
  implicit setter = sink.setter[T]
      val res = fork
      flowDef.addTrap(res.toPipe[T](sink.sinkFields), sink.createTap(Write))
      res 
    }

The main reason this doesn't work is because of how we have implemented the TypedPipe. It wraps a Pipe, and then chains together a bunch of commands, then eventually makes that into a function applied to a pipe. So in the case where you do typedPipe.map.addTrap.map, the two maps will actually be crunched together and the trap won't be applied properly. So the only way I could find to keep the semantics that we expect (and are consistent with cascading) is to go to and from the pipe, which necessitates the TupleConverter. A TypedPipe isn't a real pipe, and that's all that cascading knows.

That said, we COULD do something a bit crazier with the design. We could change the way that maps in the typed API work such that they catch errors, and we could match that with the last addTrap call. The issue with that is that we would lose cascadings current ability to have an addTrap in a pipeline with a given source... we would have to do something like typedPipeSource.addTraps(Map("a"->trap1, "b"->trap2)) etc, and we'd also lose a lot of type safety.