Great explanation Pierre!
if I may humbly add a few points. ZLayers force you to model your dependencies explicitly as well as think about your constructors. Constructors (and their dependencies) may be effectful in nature (i.e opening a database connection/creating a pool of connections). Not only this, but there is a lifecycle associated with each resource and their dependencies.
When you say
val deps: ZLayer[Any, Nothing, Dep]
effect.provide(deps)
What you are doing is initializing dependencies whose lifecycle(s) are bound to the effect. As soon as the effect completes, the dependencies are torn down.
Another way to look at it is ZLayers are essentially categorical arrows - they are powerful enough to represent functions through receiving arguments/dependencies as R, fail with an E and produce an A, in ZLayer[R, E, A] where
ZIO.scoped { R => ZIO[Scope, E, A] } where it will take care of managing the Scope for you (keeping the dependencies open for as long your effect needs).
The main points to focus on here
ZLayers are just as powerful as functions
ZLayers handle lifecycle management for your dependencies compositionally ensuring resources and their dependencies lifecycles are appropriately spun up and torn down
You may think that ZLayers are complex but try to do all of this without compositional lifecycle resource management - you'll soon find you are in a world of pain