Это последняя статья в серии о рассмотрении композиции Inoversion of Coupling Control (IoCC). Предыдущие статьи охватывали:
- Первоклассная система типов процедур
- Первоклассный модуль
- Примеры кода
В этой статье рассматривается предоставление математической модели для объяснения композиции.
Вам также может понравиться:
Функциональное программирование на чистом Java: примеры с Functor и Monad
Просто небольшая оговорка, что я не математик. У меня есть степень в области компьютерных наук, но она не охватывает много функционального программирования. Многое из этого благодаря моему самоучке в понимании функционального программирования и математики. Поэтому я открыт для отзывов более способных математиков о лучших способах выражения модели. Я надеюсь, что эта статья разумно передает основную модель для композиции с Inversion of Coupling Control.
Из теории категорий у нас есть ассоциативный закон:
Простой текст
1
f(x) . g(x) = f.g(x)
С этим мы можем ввести зависимости:
Простой текст
xxxxxxxxxx
1
f(x)(d1) . g(x)(d2) = f.g(x)(d1, d2)
… где d — это набор зависимостей.
Это делает программу очень жесткой, поскольку изменение d1 на d3 оказывает значительное влияние на использование f (x) (теперь d1 d3). Например, переключение с соединения с базой данных на конечную точку REST.
ZIO пытается уменьшить жесткость следующим образом:
Простой текст
xxxxxxxxxx
1
f(x)(d1) . g(x)(d2) = f.g(x)(D)
… где: D = d1 + d2
Или, другими словами:
D extends d1 with d2
Теперь мы можем создать множество морфизмов и при выполнении результирующего ZIO предоставить hom (D), который представляет собой набор всех необходимых зависимостей.
Итак, эта модель работает. Это, безусловно, позволяет вводить зависимости в функциональные программы.
Теперь я хотел бы принять другой такт к проблеме.
В документе « Императивное функциональное программирование» не было видно, как удалить тип продолжения (z) из подписи. Авторы пришли к выводу, что Монады и CPS очень похожи, но из-за дополнительного типа продолжения на подписи и интуиции автора, Монадой IO было направление вперед.
Теперь я определенно не берусь за такт замены IO Monad на CPS. Я ищу, чтобы создать дополнительную модель. Модель, в которой продолжения развязывают IO Monads.
Итак, вводя зависимости в монаду ввода / вывода, мы получаем:
Простой текст
xxxxxxxxxx
1
IO[x](d)
… где: d — набор требуемых зависимостей
Затем следует, что, соединяя два IO вместе, мы получаем:
Простой текст
xxxxxxxxxx
1
IO[x](d1, d2)
Так что, возможно, давайте отделим монаду IO и присоединимся к ним через CPS. Это меняет подпись на:
Простой текст
xxxxxxxxxx
1
IO[x](d)(z)
… где: z = либо [Throwable, x] -> ()
Досадно, о чем говорила статья «Императивное функциональное программирование».
Тем не менее, ранее обсуждалась инъекция продолжения . Это эффективно скрывает z от подписи, делая его внедренной функцией. Поскольку это внедренная функция, z становится косвенным указанием для другой функции. Эта косвенность может быть представлена как:
Простой текст
xxxxxxxxxx
1
IO[_](d1) -> (Either[Throwable,y] -> IO[y](d2)) -> IO[y](d2)
Примечание: присоединенный IO должен обрабатывать только y или любой из его супертипов. Следовательно, отношение указывает переданный тип. Это облегчает ввод в другой IO для обработки продолжения.
Теперь, чтобы начать изолировать монады ввода / вывода друг от друга, мы начнем с инъекции потоков .
Простой текст
xxxxxxxxxx
1
d -> Executor
Это представляет собой инжекцию потока, выбирающую подходящее Executor
из зависимостей. Следовательно, мы можем ввести монаду Thread Injection, чтобы выбрать Executor
.
Простой текст
xxxxxxxxxx
1
F[_](d)(Executor) -> (d -> Executor) -> TI[F[_](d)]
… где TI — монада внедрения потока, которая содержит зависимость от Executor
отображения, чтобы позволить выполнение монады ввода-вывода с соответствующим Executor
.
Таким образом, вышеприведенное продолжение между отношениями IO Monads становится:
Простой текст
xxxxxxxxxx
1
TI[IO[_](d1)] -> (Either[Throwable,y] -> IO[y](d2)(Executor)) -> TI[IO[y](d2)]
Теперь Executor
монады ввода-вывода могут быть выполнены соответствующим образом через монаду TI.
В дополнение к этому, мы можем моделировать внедрение зависимостей с помощью:
Простой текст
xxxxxxxxxx
1
F[_](d) -> (F[_](d) -> F[_]) -> DI[F[_]]
… где DI — это монада внедрения зависимостей, которая обеспечивает зависимости для функции.
Обратите внимание, что DI Monad также будет управлять жизненным циклом зависимостей. Обсуждение того, как это делается, станет темой для другой статьи.
Итак, вышеприведенные отношения продолжения IO Monad становятся:
Простой текст
xxxxxxxxxx
1
TI[DI[IO[_]]] -> (Either[Throwable,y] -> IO[y](d)(Executor)) -> TI[DI[IO[y]]]
Теперь, с инъекцией продолжения, мы не ограничены введением только в одном продолжении. Мы можем вводить во многих:
Простой текст
xxxxxxxxxx
1
TI[DI[IO[_]]] -> (Either[Throwable,y] -> IO[y](d)(Executor)) -> TI[DI[IO[y]]]
2
-> (Either[Throwable,w] -> IO[w](d)(Executor)) -> TI[DI[IO[w]]]
4
...
Примечание: я предполагаю, что это может быть представлено в одной строке (возможный актив продолжения от конкретного ввода / вывода), но я оставлю это более математически, чем я.
Это означает, что мы можем удалить Either и обработать (возможно много) исключений отдельными продолжениями, чтобы получить:
Простой текст
xxxxxxxxxx
1
TI[DI[IO[_]]] -> (y -> IO[y](d)(Executor)) -> TI[DI[IO[y]]]
2
-> (ex -> IO[ex](d)(Executor)) -> TI[DI[IO[ex]]]
4
...
Это демонстрирует, что IO может теперь иметь более одного выхода. Имея возможность вводить несколько продолжений, IO может иметь несколько выходов.
Это также безопасное исполнение. OfficeFloor (Inversion of Coupling Control) обеспечивает обработку одного продолжения до того, как начнется выполнение следующего продолжения. Это гарантирует, что одновременно выполняется только один ввод-вывод.
В дополнение к этому, мы можем квалифицировать DI. Первоначально у нас был d1, d2, который был скрыт DI. Мы можем определить область действия DI следующим образом:
Простой текст
xxxxxxxxxx
1
DI[P,T,_]
... где:
P - множество экземпляров зависимости процесса
T - множество экземпляров зависимостей потоков
Это позволяет следующее:
Простой текст
xxxxxxxxxx
1
Same thread = DI[P,T,_] -> (_ -> _) -> DI[P,T,_]
2
Spawned thread = DI[P,T,_] -> (_ -> _) -> DI[P,S,_]
4
New process = DI[P,T,_] -> (_ -> _) -> DI[Q,S,_]
Другими словами:
- Порождение потока создает новый набор экземпляров зависимостей потока
- Межпроцессное взаимодействие - это другой набор экземпляров зависимостей процессов
В дополнение к этому:
- Множество T может быть одинаковым, только если множество P одинаково
- Контекст (например, транзакции) применяется только к зависимостям в T
Результирующее отношение IO Monad для того же продолжения потока становится:
Простой текст
xxxxxxxxxx
1
TI[DI[P,T,IO[_]]] -> (y -> IO[y](d)(Executor)) -> TI[DI[P,T,IO[y]]]
... в то время как отношение продолжения порожденного потока моделируется следующим образом:
Простой текст
xxxxxxxxxx
1
TI[DI[P,T,IO[_]]] -> (y -> IO[y](d)(Executor)) -> TI[DI[P,S,IO[y]]]
По сути, это позволяет многопоточность параллелизма. Любое продолжение может породить новый поток, запустив новый набор зависимостей потока. Кроме того, OfficeFloor будет асинхронно обрабатывать продолжение возврата управления немедленно. Это имеет эффект порождения потока.
То же самое касается порождения нового процесса.
Простой текст
xxxxxxxxxx
1
TI[DI[P,T,IO[_]]] -> (y -> IO[y](d)(Executor)) -> TI[DI[Q,S,IO[y]]]
Поэтому в OfficeFloor процессы и потоки являются конфигурацией, а не проблемой программирования. От разработчиков больше не требуется кодировать безопасность потоков в свой возможный императивный код в IO. Поскольку редко используемые зависимости процессов кодируются как поточно-ориентированные, эта взаимосвязь дает возможность изменчивости в IO, которая является поточно-ориентированной. Изоляция зависимостей предотвращает повреждение памяти (при условии, что зависимости не разделяют статическое состояние).
OfficeFloor (Inversion of Coupling Control) в этом смысле, возможно, темная сторона. Функциональное программирование стремится к чистоте функций, являющихся светом. Данный OfficeFloor может обрабатывать:
- Несколько выходов от IO, включая исключения как продолжения
- Изменчивость внутри IO, которая является поточно-ориентированной
OfficeFloor позволяет моделировать более темные примеси (или, может быть, я просто смотрю слишком много «Звездных войн»).
Теперь у нас есть возможная «инверсия» функции:
Функция : стремится быть чистым, может иметь несколько входов и только один выход.
IoCC : допускает примеси, имеет один вход и может иметь несколько выходов.
Мне лично нравится думать о функциях как о частях машины. Они являются высокопарными шестернями двигателя, обеспечивающими всегда предсказуемую производительность.
Затем мне нравится думать о IoCC как о сигналах. Это более органичная структура слабо связанных событий, запускающих другие слабо связанные события. Результатом является в основном предсказуемый результат. Это больше похоже на результаты принятия решений человеком.
Regardless, we now have a typed model that can be represented as a directed graph of interactions. The IO Monads are the nodes with the various continuations edges between them. An edge in the graph is qualified as follows.
xxxxxxxxxx
TI[DI[IO]] == y,p,t ==> TI[DI[IO]]
... where:
y indicates the data type provided to the continuation
p indicates if a new process is spawned (represented as 0 for no and 1 for yes)
t indicates if a new thread is spawned (again represented as 0 for no and 1 for yes)
The result is the following example graph.
Summary
All of the above is already implemented in OfficeFloor.
The previous articles demonstrated the type system of Inversion of Coupling Control to enable composition. The type system also enabled encapsulation in First-Class Modules for easy assembly and comprehension of OfficeFloor applications. This was then demonstrated with a simple example application.
What this article has attempted to cover is the core underlying model. It has looked at how injected continuations can be used to join together IO instances. Further, it looked at the dependencies and how they can be used to model processes and threads.
Further Reading
Functional Programming in Pure Java: Functor and Monad Examples