Идиоматический Scala для применения функций в цепочке, если определены параметры

Есть ли уже существующий/Scala-идиоматический/лучший способ сделать это?

def sum(x: Int, y: Int) = x + y

var x = 10
x = applyOrBypass(target=x, optValueToApply=Some(22), sum)
x = applyOrBypass(target=x, optValueToApply=None, sum)
println(x) // will be 32

Мой applyOrBypass можно определить так:

def applyOrBypass[A, B](target: A, optValueToApply: Option[B], func: (A, B) => A) = {
  optValueToApply map { valueToApply =>
    func(target, valueToApply)
  } getOrElse {
    target
  }
}

В основном я хочу применять операции в зависимости от того, определены ли определенные значения Option или нет. Если это не так, я должен получить ранее существовавшее значение. В идеале я хотел бы связать эти операции и не использовать var.

Моя интуиция подсказывает мне, что потребуется свернуть или уменьшить, но я не уверен, как это будет работать. Или, может быть, есть другой подход с monadic-fors...

Любые предложения/подсказки приветствуются!


person Sebastian N.    schedule 29.12.2013    source источник


Ответы (4)


В Scala есть способ сделать это с помощью для понимания (синтаксис похож на нотацию haskell do, если вы с ней знакомы):

(for( v <- optValueToApply ) 
  yield func(target, v)).getOrElse(target)

Конечно, это более полезно, если у вас есть несколько переменных, существование которых вы хотите проверить:

(for( v1 <- optV1
    ; v2 <- optV2
    ; v3 <- optV3
    ) yield func(target, v1, v2, v3)).getOrElse(target)

Если вы пытаетесь накопить значение по списку опций, я бы порекомендовал свернуть, чтобы ваша необязательная сумма выглядела так:

val vs = List(Some(1), None, None, Some(2), Some(3))

(target /: vs) ( (x, v) => x + v.getOrElse(0) )
  // => 6 + target

Вы можете обобщить это при условии, что ваша операция func имеет некоторое значение идентификатора, identity:

(target /: vs) ( (x, v) => func(x, v.getOrElse(identity)) )

Математически говоря, это условие состоит в том, что (func, identity) образует моноид. Но это между прочим. Фактический эффект заключается в том, что всякий раз, когда достигается None, применение к нему func и x всегда будет давать x (значения None игнорируются, а значения Some разворачиваются и применяются как обычно), что вам и нужно.

person amnn    schedule 29.12.2013

В таком случае я бы использовал частично примененные функции и identity:

def applyOrBypass[A, B](optValueToApply: Option[B], func: B => A => A): A => A =
  optValueToApply.map(func).getOrElse(identity)

Вы бы применили это так:

def sum(x: Int)(y: Int) = x + y

var x = 10
x = applyOrBypass(optValueToApply=Some(22), sum)(x)
x = applyOrBypass(optValueToApply=None, sum)(x)
println(x)
person Robin Green    schedule 29.12.2013

Да, вы можете использовать fold. Если у вас есть несколько необязательных операндов, я считаю, что в библиотеке Scalaz есть несколько полезных абстракций.

var x = 10
x = Some(22).fold(x)(sum(_, x))
x = None    .fold(x)(sum(_, x))
person 0__    schedule 29.12.2013

Если у вас есть несколько функций, это можно сделать с помощью Scalaz.

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

Сначала добавьте импорт:

import scalaz._, Scalaz._

Затем создайте свои функции (этот способ не стоит того, если ваши функции всегда одинаковы, но если они разные, это имеет смысл)

val s = List(Some(22).map((i: Int) => (j: Int) => sum(i,j)),
             None    .map((i: Int) => (j: Int) => multiply(i,j)))

Наконец, примените их все:

(s.flatten.foldMap(Endo(_)))(x)
person Robin Green    schedule 26.06.2015