Ваше сомнение понятно, так как да, оба подхода имеют один и тот же результат: неявное прохождение зависимостей через весь стек вызовов, поэтому вам не нужно явно передавать их на каждом уровне. При обоих подходах вы передадите свои зависимости один раз от внешнего края, и все.
Допустим, у вас есть функции a (), b (), c () и d (), и давайте скажем, каждый вызывает следующий: a () -> b () -> c () -> d (). Это наша программа.
Если вы не использовали ни один из упомянутых механизмов, и вам нужны были некоторые зависимости в d (), вы бы в конечном итоге перенаправили свои зависимости (давайте назовем их ctx) до самого конца на каждом уровне:
a(ctx) -> b(ctx) -> c(ctx) -> d(ctx)
Хотя после использования любого из упомянутых двух подходов это будет выглядеть так:
a(ctx) -> b() -> c() -> d()
Но все же, и это важно помнить вы бы имели свои зависимости, доступные в области действия каждой из этих функций. Это возможно, потому что с описанными подходами вы включаете окружающий контекст, который автоматически пересылает их на каждом уровне, и каждая из функций выполняется внутри. Поэтому, находясь в этом контексте, функция получает видимость этих зависимостей.
Читатель: Это тип данных. Я рекомендую вам прочесть и попытаться понять этот глоссарий, в котором объясняются типы данных, поскольку различие между этими двумя подходами требует понимания того, что такое классы типов и типы данных, и как они взаимодействуют друг с другом: *
В качестве сводки, типы данных представляют контекст для данных программы. В этом случае Reader обозначает вычисление, которое требует некоторых зависимостей для запуска. Т.е. вычисления типа (D) -> A. Благодаря его flatMap / map / и другим его функциям и тому, как они кодируются, D будет передаваться неявно на каждом уровне, и так как вы будете определять каждую из ваших программных функций как Reader, вы всегда будете работать в контексте Reader, поэтому получите доступ к необходимым зависимостям (ctx). Т.е.:
a(): Reader<D, A>
b(): Reader<D, A>
c(): Reader<D, A>
d(): Reader<D, A>
Таким образом, связывая их с помощью доступных в Reader комбинаторов, таких как flatMap или map, вы получите, что D будет неявно пропущен и включен (доступен) для каждого из этих уровней.
С другой стороны, подход, описанный в посте Пако, выглядит по-другому, но в итоге получается то же самое. Этот подход заключается в использовании функций расширения Kotlin, поскольку определение программы для работы с типом получателя (назовем его Context) на всех уровнях будет означать, что каждый уровень будет иметь доступ к упомянутому контексту и его свойствам. Т.е.:
Context.a()
Context.b()
Context.c()
Context.d()
Обратите внимание, что получатель функции расширения - это параметр, который без поддержки функции расширения вам нужно будет вручную передавать в качестве дополнительного аргумента функции при каждом вызове, поэтому в этом случае есть зависимость, или «контекст», который требуется для запуска функции. Понимая их таким образом и понимая, как Kotlin интерпретирует функции расширения, получателю не нужно будет перенаправлять его вручную на каждом уровне, а просто передавать его на край входа:
ctx.a() -> b() -> c() -> d()
B, c и d будет вызываться неявно, без необходимости явного вызова каждой функции уровня через получатель, поскольку каждая функция уже выполняется внутри этого контекста, следовательно, она имеет доступ к своим свойствам (зависимостям), включенным автоматически. мы понимаем, что нам нужно выбрать один или любой другой подход DI. Это весьма субъективно, поскольку в функциональном мире есть и другие альтернативы для внедрения зависимостей, такие как конечный подход без тегов, который опирается на классы типов и их разрешение во время компиляции, или EnvIO, который все еще недоступен в Arrow, но скоро будет (или эквивалентная альтернатива). Но я не хочу, чтобы ты запутался здесь. По моему мнению, Reader немного "шумит" в сочетании с другими распространенными типами данных, такими как IO, и я обычно стремлюсь к конечным подходам без тегов, поскольку они позволяют сохранять программные ограничения, определяемые классами введенных типов, и полагаются на время выполнения IO для полного Ваша программа.
Надеюсь, это немного помогло, в противном случае не стесняйтесь спрашивать снова, и мы вернемся, чтобы ответить ?