Как обрабатывать ошибки в Рамде - PullRequest
4 голосов
/ 19 марта 2019

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

const targetColumnIndexes = targetColumns.map(h => {
    if (header.indexOf(h) == -1) {
      throw new Error(`Target Column Name not found in CSV header column: ${h}`)
    }
    return header.indexOf(h)
  })

Для справки, это значения header и targetColumns

const header = [ 'CurrencyCode', 'Name', 'CountryCode' ]
const targetColumns = [ 'CurrencyCode', 'Name' ]

Так что мне нужно:

  • отобразить на targetColumns
  • вернуть indexOf targetColumn из заголовка
  • выдать ошибку, если индекс -1

Ответы [ 2 ]

6 голосов
/ 19 марта 2019

Как говорит customcommander, есть веская причина, по которой этот стиль создания исключений не облегчается функциональным программированием: о нем гораздо сложнее рассуждать.

«Что возвращает функция?»

«Число».

«Всегда?»

«Да, ...ну, если только он не выдает исключение. "

" Что он возвращает? "

" Ну, это не так. "

" Таким образом, он возвращает число или ничеговообще? "

" Полагаю, что так. "

" Хммм. "

Одной из самых распространенных операций в функциональном программировании является составление двух функций.Но это работает, только если выходные данные одной функции совпадают с входными данными ее преемника.Это сложно, если первый может выдать исключение.

Чтобы справиться с этим, мир FP использует типы, которые фиксируют понятия неудачи.Возможно, вы видели разговор о типе Maybe, который обрабатывает значения, которые могут быть null.Другим распространенным является Either (иногда Result), который имеет два подтипа, для случая ошибки и успешного (соответственно Left и Right для Either или Error и Ok для Result.) В этих типах первая найденная ошибка перехватывается и передается по линии тому, кто в ней нуждается, в то время как успешный пример продолжает обрабатываться.(Существуют также Validation типы, которые фиксируют список ошибок.)

Существует много реализаций этих типов.См. Список предложений fantasy-land .

У Рамды был свой набор этих типов, но он отказался от его обслуживания.Мы часто рекомендуем для этого народные сказки и святилище.Но даже старая реализация Рамды должна подойти.Эта версия использует Folktale's data.either, так как я знаю это лучше, но более поздние версии Folktale заменяют его на Result.

Следующий блок кода показывает, как я мог бы использовать Either s для обработки этого понятия отказа, особенно как мы можем использовать R.sequence для преобразования массива Eithers вEither держит массив.Если на входе есть какие-либо значения Left с, то на выходе будет просто Left.Если это все Right с, то на выходе получается Right, содержащий массив их значений.Благодаря этому мы можем преобразовать все имена наших столбцов в Either s, которые фиксируют значение или ошибку, но затем объединяют их в один результат.

Следует отметить, что здесь не выдается никаких исключений.Наши функции составятся правильно.Понятие ошибки заключено в типе.

const header = [ 'CurrencyCode', 'Name', 'CountryCode' ]

const getIndices = (header) => (targetColumns) => 
  map((h, idx = header.indexOf(h)) => idx > -1
    ? Right(idx)
    : Left(`Target Column Name not found in CSV header column: ${h}`)
  )(targetColumns)

const getTargetIndices = getIndices(header)

// ----------

const goodIndices = getTargetIndices(['CurrencyCode', 'Name'])

console.log('============================================')
console.log(map(i => i.toString(), goodIndices))  //~> [Right(0), Right(1)]
console.log(map(i => i.isLeft, goodIndices))      //~> [false, false]
console.log(map(i => i.isRight, goodIndices))     //~> [true, true]
console.log(map(i => i.value, goodIndices))       //~> [0, 1]

console.log('--------------------------------------------')

const allGoods = sequence(of, goodIndices)

console.log(allGoods.toString())                  //~> Right([0, 1])
console.log(allGoods.isLeft)                      //~> false
console.log(allGoods.isRight)                     //~> true
console.log(allGoods.value)                       //~> [0, 1]

console.log('============================================')

//----------

const badIndices = getTargetIndices(['CurrencyCode', 'Name', 'FooBar'])

console.log('============================================')
console.log(map(i => i.toString(), badIndices))   //~> [Right(0), Right(1), Left('Target Column Name not found in CSV header column: FooBar')
console.log(map(i => i.isLeft, badIndices))       //~> [false, false, true]
console.log(map(i => i.isRight, badIndices))      //~> [true, true, false]
console.log(map(i => i.value, badIndices))        //~> [0, 1, 'Target Column Name not found in CSV header column: FooBar']


console.log('--------------------------------------------')

const allBads = sequence(of, badIndices)          
console.log(allBads.toString())                   //~> Left('Target Column Name not found in CSV header column: FooBar')
console.log(allBads.isLeft)                       //~> true
console.log(allBads.isRight)                      //~> false
console.log(allBads.value)                        //~> 'Target Column Name not found in CSV header column: FooBar'
console.log('============================================')
.as-console-wrapper {height: 100% !important}
<script src="//bundle.run/ramda@0.26.1"></script>
<!--script src="//bundle.run/ramda-fantasy@0.8.0"></script-->
<script src="//bundle.run/data.either@1.5.2"></script>
<script>
const {map, includes, sequence} = ramda
const Either = data_either;
const {Left, Right, of} = Either
</script>

Главное для меня то, что такие значения, как goodIndices и badIndices, полезны сами по себе.Если мы хотим сделать больше обработки с ними, мы можем просто map над ними.Например, обратите внимание, что

map(n => n * n, Right(5))     //=> Right(25)
map(n => n * n, Left('oops')) //=> Left('oops'))

Таким образом, наши ошибки оставлены в покое, и наши успехи обрабатываются далее.

map(map(n => n + 1), badIndices) 
//=> [Right(1), Right(2), Left('Target Column Name not found in CSV header column: FooBar')]

И это то, что эти типы все о.

0 голосов
/ 21 марта 2019

Я собираюсь предложить особое мнение: Either - хорошее решение для того, чтобы колотить квадратные колышки через круглые отверстия системы статического типа.Это большая когнитивная нагрузка и меньшая выгода в JavaScript (отсутствие гарантий правильности).

Если рассматриваемый код должен быть быстрым (как доказано профилированием и задокументированным бюджетом производительности), вы должны написать его вимперативный стиль.

Если он не должен быть быстрым (или достаточно быстрым при реализации следующего), то вы можете просто проверить, что вещь, которую вы повторяете, является правильным подмножеством (или точным соответствиемfor) из заголовков CSV:

// We'll assume sorted data. You can check for array equality in other ways,
// but for arrays of primitives this will work.
if (`${header}` !== `${targetColumns}`) throw new Error('blah blah');

Это дает четкое разделение проблем от проверки правильности данных и выполнения желаемого преобразования.

Если все, что вас беспокоит, этодлина, затем просто проверьте это и т. д.

...