The mtl technique subsumes the free monad technique. if you have a term:
getCurrentTime :: MonadClock m => m UTCTime
Then you can *use* that function as anything that satisfies the constraint. Given an `IO` instance, that can just get the current time: `getCurrentTime :: IO UTCTime`. Given a mock instance, that can be `getCurrentTime :: MockClock UTCTime`. Given an instance on Free, you'd have `getCurrentTime :: Free CurrentTimeF UTCTime`
I generally find it more pleasant to write functions in mtl style. If you're after more concrete guarantees on the DSL you're building and see yourself doing a lot of introspection and optimization, then a Free monad approach fits the bill.