Монада
Это действительно отличная концепция, которая развилась за последние 25 лет и позволяет нам не обращать внимания на проблемы внутреннего состояния при каскадном преобразовании данных. Чтобы лучше понять это, давайте начнем с предположения, что у нас есть две функции, подобные этой:
def addOne(x: Int): Int = x + 1 def square(x: Int): Int = x * x
и мы хотим выполнить следующие преобразования:
addOne(square(addOne(square(x))))
Легко ли это оценить? Да, конечно! Но что, если я спрошу, что происходит под капотом на каждом этапе трансформации, например. записывать каждый шаг? Мы не можем сделать это так просто, нам нужно изменить функции, чтобы добавить ведение журнала в их соответствующую реализацию, что немного болезненно, но все же выполнимо, как это
def addOne(num: Int): Int = { println(s"Adding 1 to $num") num + 1 } def sqaure(num: Int): Int = { println(s"Doing sqaure of $num") num * num }
Представьте, что есть сотни таких функций, и мы хотим знать не только ведение журнала, но и несколько дополнительных деталей или пользовательских действий между шагами, например: если какой-либо из шагов терпит неудачу, не прерывайте все преобразование, а вместо этого выполняйте определенное действие в таком случае. случаях и держать исключения в стеке до тех пор, пока выполнение не завершится. Я имею в виду, что это может быть все, о чем мы можем попросить, верно? Я привел только один пример выше из всех таких безграничных возможностей. Нам на помощь приходит Monad, которая не заботится о том, что написано внутри каждой функции, а излагает абстрактные возможности для выполнения пользовательских функций и добавления изменений в одном месте, которые влияют на все, что мы хотели. Посмотрите следующий код о том, как мудро мы собираем вещи в одном месте.
trait ResultWrapper { val num: Int val log: String } def addOne(num: Int): ResultWrapper = { new ResultWrapper { override val num = num + 1 override val log = s"Adding 1 to $num" } } def sqaure(num: Int): ResultWrapper = { new ResultWrapper { override val num = num * num override val log = s"Doing sqaure of $num" } } def run(input: ResultWrapper, transform: _: Int => ResultWrapper): ResultWrapper = { val result: ResultWrapper = transform(input.num) new ResultWrapper { override val num = result.num override val log = s"{input.log} >> ${result.log}" } }
Теперь он больше не тесно связан и дает нам возможность добавлять дополнительные действия, просто обновляя трейт ResultWrapper и метод run. Итак, настоящий вопрос: «Где здесь Монада?» Итак, мы только что написали монаду выше! (СЮРПРИЗ😲). Мы можем дополнительно изменить приведенный выше код, чтобы сделать его более универсальным, где тип может быть не ограничен примитивными, а также алгебраическими и более специфическими пользовательскими типами, например:
trait ResultWrapper[T, U]{ val value: T def apply(input: T => U): U }
а затем соответствующим образом измените реализации. Ради краткости темы я сделал вещи простыми, но полными. Я надеюсь, что вы поняли это и добавили немного больше к своим знаниям сегодня.
Спасибо за прочтение. Ваше здоровье.!