We often think about abstractions in terms of "what it is" but the important part that we often forgot about is to think in terms of "what it is not".
In both extremes such abstractions are pretty useless.
Obviously the more abstraction is concret the less things it can represent.
Because there is a reality behind the abstraction. The concrete implementation must satisfy the constraints so that client can use it without fear.
Being familiar with .NET world I saw interfaces like ISqlRepository
What is the point of such abstraction at all ?
It is too close to the implementation so almost useless.
Do Sql and EventSourced implementation can be described in terms of the same abstraction for a repository. Seems like its not that easy
We said that either we must sacrify the properties we know about concrete implementation to build a more powerful abstraction, or to keep those properties with a risk of breaking Liskov Substitution principle
Let's take an example from .NET. Everybody has already used an abstraction like IDictionary<TKey, TValue>
But what if the concrete implementation is and ImmutableDictionary ?
Similar design choices where made in .NET framework for IList<T> interface
So IList<T> encourages creating types that are not substitutable for each other, breaking LSP. Such types you have to check the state of flags to determine which implementation one is handling.
I have given some examples from OOP world, but believe me FP design has exactly the same problems of choosing the right level of abstraction.
Choosing wisely the right level of abstraction is hard. It has implications whatever direction you take.
To find a better balance consider what an abstraction "is" and what "it is not"