They're different representations of the same thing.
This is useful because we can do easy error handling in the EitherIO monad but we can't run it. We can run the IO monad but handling errors in it sucks ass.
So we handle errors in EitherIO, and make it runnable (ie. convert to the IO monad) by calling runEitherIO.
It's like calling a function that takes a pair when some other API gives you a two element list. The same kind of data, just munging the shape so things work. In pseudocode:
// EitherIO and runEitherIO in this analogy.
fun toList((a, b)) -> [a, b]
fun toPair([a, b]) -> (a, b)
// Gotta convert to a "runnable" pair before calling the function.
var latLngList = [blah, blah]
var resultPair = api1ThatRequiresPair(toPair(latLngList))
// Convert representations to do operations with a different API.
api2ThatRequiresList(toList(resultPair))
This is useful because we can do easy error handling in the EitherIO monad but we can't run it. We can run the IO monad but handling errors in it sucks ass.
So we handle errors in EitherIO, and make it runnable (ie. convert to the IO monad) by calling runEitherIO.
It's like calling a function that takes a pair when some other API gives you a two element list. The same kind of data, just munging the shape so things work. In pseudocode: