---
title: Javascript и монада Maybe
slug: javascript-maybe-monad
canonical: https://shady2k.ru/posts/javascript-maybe-monad/
date: 2021-03-30
kind: article
author: human
lang: ru
summary: Пишу свою монаду Maybe на JavaScript, чтобы безопасно доставать
  вложенные свойства там, где старый Node.js ещё не умеет опциональные цепочки.
tags: []
---

В какой-то момент мне надоело писать что-то типа `object && object.prop1 && object.prop1.prop2`. Это ужасно и неправильно. Кроме очевидного варианта с перехватом исключения try/catch, нашлись опциональные цепочки, и, конечно же, монады.

В новых версиях JS появились опциональные цепочки `?.`, безопасный способ доступа к свойствам вложенных объектов, даже если какое-либо из промежуточных свойств не существует, то есть вместо `user && user.address && user.address.street`можно написать `user?.address?.street`. Но в моем случае этот вариант не подошел, использовалась старая версия Node.js.

Во-первых, а что такое монада? Монада — это особый тип данных в функциональных языках программирования. Про функциональное программирование я только слышал и считал, что это не про Javascript, а оказалось, что даже на вики написано:

> Поддерживает объектно-ориентированный, императивный и функциональный стили.

Вот про функциональный стиль Javascript мы сегодня и поговорим. Я не буду углубляться в определение что такое монада и цитировать умные книжки, а расскажу как я понял её сам. Монада — это что-то вроде контейнера, который поддерживает определенные стандартами операции. Самый первый стандарт, который находит Google — это [Fantasy Land](https://github.com/fantasyland/fantasy-land).

В общем и целом, монада должна поддерживать как минимум:

- map
- join
- chain
- ap

Это позволяет соединять различные (или даже одинаковые) монады в цепочки, так как все они следуют единому стандарту.

Есть куча готовых библиотек с различными готовыми монадами, но это не наш метод, чтобы разобраться как это работает — нужно написать свою версию монады Maybe. Ниже одна из реализаций класса Maybe, уверен, что не полная, а может быть и где-то неправильная, но моя.

```javascript
class Maybe {
  constructor(val) {
      this.__value = val;
  }

  static of(val) {
      return new Maybe(val);
  }

  static Nothing() {
      return Maybe.of(null);
  }

  flatMap(fn){
      if(this.isNothing()) return Maybe.Nothing();
      const m = fn(this.__value);

      return m.isNothing() ? 
           Maybe.Nothing() : 
           Maybe.of(m.__value);
  }

  getOrElse(elseVal) {
      return this.isNothing() ? elseVal : this.__value;
  }

  getOrEmptyArray() {
      return this.getOrElse([]);
  }

  getOrNull() {
      return this.getOrElse(null);
  }

  isNothing() {
      return this.__value === null || this.__value === undefined;
  }

  map(fn) {  
      return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this.__value));
  }

  join() {
      return this.__value;
  }

  chain(fn) {
      return this.map(fn).join();
  }

  ap(someOtherMaybe) {
      return someOtherMaybe.map(this.__value);
  }
}
```

Ну и, по сути, вся проверка на существование свойства объекта происходит банальным `this.__value === null || this.__value === undefined`.  
А теперь попробуем монаду Maybe в деле:

![](/images/javascript-maybe-monad/01.webp)

Подключим необходимые модули, для работы с `path` используется библиотека [rambda](https://github.com/selfrefactor/rambda):

```javascript
import { path } from 'rambda'
```

Определяем объект `testObj`:

```javascript
const testObj = {
  "a": {
      "b": {
          "c": "test"
      }
  }
}
```

А затем создаем экземпляр нашего класса `Maybe`.

```javascript
const maybeObj = Maybe.of(testObj)
```

Для того, чтобы получить строку, используется функция `join()`.  
А здесь мы пытаемся получить значение свойства `c`:

```javascript
const val = maybeObj
  .map(path(["a", "b", "c"]))
  .join()
```

Далее попытка обратиться к несуществующему свойству не приведет к возникновению ошибки, а просто вернет `undefined`.

Полный исходный код ниже:

```javascript
import { path } from 'rambda'

const testObj = {
    "a": {
        "b": {
            "c": "test"
        }
    }
}

const maybeObj = Maybe.of(testObj);
console.log(maybeObj);
console.log(maybeObj.join());

const val = maybeObj
    .map(path(["a", "b", "c"]))
    .join();
console.log(val);

const valNothing = maybeObj
    .map(path(["test", "b", "c"]))
    .join();
console.log(valNothing);
```

Полезные ссылки:

- [https://jrsinclair.com/articles/2016/marvellously-mysterious-javascript-maybe-monad/](https://jrsinclair.com/articles/2016/marvellously-mysterious-javascript-maybe-monad/)
- [https://medium.com/@yyankowski/maybe-monad-in-javascript-to-save-us-from-the-hell-of-the-null-guard-clauses-bc9f9a1f291b](https://medium.com/@yyankowski/maybe-monad-in-javascript-to-save-us-from-the-hell-of-the-null-guard-clauses-bc9f9a1f291b)
- [https://github.com/stoeffel/awesome-fp-js](https://github.com/stoeffel/awesome-fp-js)
- [https://tproger.ru/translations/better-javascript-code-with-fp-features/](https://tproger.ru/translations/better-javascript-code-with-fp-features/)
