First, we can clean that code:
do xs <- mapM (readFile . fpFromString) (files opts)
hPut stdout (process opts (concat xs))
You won't be able to make the decision of which type process should work
at inside of process because this decision will also affect which
implementation of readFile is used.
What you will need to do is inspect the 'opts' in the function above
and execute it at a type as determined by the options.
example opts
| isTextMode opts = aboveFunctionAsText opts
| isByteStringMode opts = aboveFunctionAsByteString opts
If you'd like to unify these into a single implementation as above
you'll need to provide some kind of extra information to resolve
the type ambiguity. This would have a type like:
aboveFunctionWithProxy :: IsSequence a => proxy a -> Options -> IO ()
exampleUsingProxies opts
| isTextMode opts = aboveFunctionWithProxy (Proxy :: Proxy Text) opts
| isByteStringMode opts = aboveFunctionWithProxy (Proxy :: Proxy ByteString) opts
The proxy parameter could then need to be linked to the type of the
internal xs parameter above using either scoped type variables or
a helper function similar to 'asTypeOf' with with a different type!