via is just a shortcut for viaMat(...)(Keep.left), and in fact this is how it's implemented: override def via[T, Mat2](flow: Graph[FlowShape[Out, T], Mat2]): Repr[T] = viaMat(flow)(Keep.left)
toMat is the same as viaMat but for sinks, it lets you keep the materialized value from the left (source/flow) or right (sink) side or both
Keep.both is just an alias for (a:A,b:B) => (a, b)
, that is a function that takes the two input parameters and return them as a tuple. It's used to have the materialized value of both left and right side when combining two flows (or source and flow or flow and sink etc)
I'll dissect your line of code:
// you're keeping the materialized value of flow
val source2 = Source (1 to 10).viaMat(flow)(Keep.right)
// you're keeping both materialized values, i.e. the one of flow from previous step
// and the one o sink.
val runnableGraph = source2.toMat(sink)(Keep.both)
runnableGraph.run() // returns a tuple (flowMatVal, sinkMatVal)
When you join two parts of a flow (i.e source and flow/sink or flow and sink) each of them has a materialized value that you get when you run the flow. The default behaviour when combining with via/to is keeping the left side. If you use viaMat/toMat you can choose to keep the right materialized value or both of them as a tuple.