Монада

Это действительно отличная концепция, которая развилась за последние 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
}

а затем соответствующим образом измените реализации. Ради краткости темы я сделал вещи простыми, но полными. Я надеюсь, что вы поняли это и добавили немного больше к своим знаниям сегодня.

Спасибо за прочтение. Ваше здоровье.!