Not really. There are various DI patterns you can use in F# [1], but the combination of no cyclic dependencies + type inference does mean you can just do the simplest thing - pass dependencies manually as specific arguments - and it will stay manageable for much longer than in java/c#.
Evil dangerous code using global variables:
let mail = // .. create email service
let db = // .. create database service
let receiveThing thing = async {
let query = // .. compose update query
do! saveToDb db query
let emailText = // .. compose email
do! sendMail mail emailText
}
while readInput() do receiveThing (getThing())
Beautiful pure code using dependency injection:
let mail = // .. create email service
let db = // .. create database service
let receiveThing db mail thing = async {
let query = // .. compose update query
do! saveToDb db query
let emailText = // .. compose email
do! sendMail mail emailText
}
while readInput() do receiveThing db mail (getThing())
All DI libraries can be ripped out and replaced with a bog standard main method, and it doesn't even require much skill. Most of what it's doing for you is stuff like "foo = new Foo(); bar = new Bar(foo, 33);" So there's no strict "need" for them, they help with (and enable) complexity.
What F#'s aversion to cyclic dependencies does forces you to break cycles of types referring to types referring to types. The way you do that is by writing a generic function that avoids referring to a concrete type. Because it's generic, such a function is inherently easier to test by means of a simple stub value.
Evil dangerous code using global variables:
Beautiful pure code using dependency injection: [1] https://fsharpforfunandprofit.com/posts/dependencies/