It provides zero abstraction, which is especially painful when handling errors. In Go you basically _have_ to handle the error right when you receive it, there is no way of usefully combining or composing it. Also error handling itself is very verbose and can't be abstracted over.
In other languages you can compose errors just as regular values, which allows you to handle the errors at the place where you have all the information to deal with it.
Not only that, the types represent the structure of the computation you ran.
For example, your type is Future[Option[List[Person]]] and just by looking at the return types of the things you called, you can tell exactly _where_ an issue occurred and how to handle it:
Your value is a Success(None)? -> The method inside your asynchronous computation that returned Option[List] failed!
Just to note, Future[Option[List[Person]]] is not some made-up example, it could be a real-world query against the database–for instance "do we know the friends of user X"?
- Future – we run it async
- Option – we might not know it
- List – his friends
This let's us very neatly tell apart things like
- Failure – "the database response from the database timed out"
- Success(None) – "we have no idea about user X' friends"
- Success(Some(Nil)) – "X has no friends"
- Success(Some(List(...))) – "here are X' friends"
(And the compiler will make sure that you handle all possibilities)
In Go the closest pattern would likely be to extract the string from the error value and compare it to error strings you recognize. An alternative would be to define a special single-purpose data type specially for each type including all combinators, as Go would not be able to express any commonality between Future[Option[List[Person]]] and Future[Option[List[Pet]]]. You would need to define 4 new data types and all operations anew.
In other languages you can compose errors just as regular values, which allows you to handle the errors at the place where you have all the information to deal with it.
Not only that, the types represent the structure of the computation you ran.
For example, your type is Future[Option[List[Person]]] and just by looking at the return types of the things you called, you can tell exactly _where_ an issue occurred and how to handle it:
Your value is a Success(None)? -> The method inside your asynchronous computation that returned Option[List] failed!
Just to note, Future[Option[List[Person]]] is not some made-up example, it could be a real-world query against the database–for instance "do we know the friends of user X"?
This let's us very neatly tell apart things like (And the compiler will make sure that you handle all possibilities)In Go the closest pattern would likely be to extract the string from the error value and compare it to error strings you recognize. An alternative would be to define a special single-purpose data type specially for each type including all combinators, as Go would not be able to express any commonality between Future[Option[List[Person]]] and Future[Option[List[Pet]]]. You would need to define 4 new data types and all operations anew.