Открытие функционального алгоритма из изменяемого - PullRequest
4 голосов
/ 11 мая 2010

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

Учитывая этот набор требований:

  • Входные данные поступают из практически бесконечного потока случайных чисел от 1 до 10

  • Окончательный результат - либо УСПЕШНЫЙ, либо СБОЙ

  • Может быть несколько объектов, «слушающих» поток в любое конкретное время, и они могут начать слушать в разное время, поэтому у всех них может быть разное понятие «первого» числа; следовательно, слушатели потока должны быть отделены от самого потока.

псевдокод:

if (first number == 1) SUCCEED
else if (first number >= 9) FAIL
else {
  first = first number
  rest  = rest of stream
  for each (n in rest) {
    if (n == 1) FAIL
    else if (n == first) SUCCEED
    else continue
  }
}

Вот возможная изменчивая реализация:

sealed trait Result
case object Fail extends Result
case object Succeed extends Result
case object NoResult extends Result

class StreamListener {
  private var target: Option[Int] = None

  def evaluate(n: Int): Result = target match {
    case None =>
      if (n == 1) Succeed
      else if (n >= 9) Fail
      else {
        target = Some(n)
        NoResult
      }

    case Some(t) =>
      if (n == t) Succeed
      else if (n == 1) Fail
      else NoResult
  }
}

Это будет работать, но пахнет для меня. StreamListener.evaluate не является ссылочно прозрачным. И использование токена NoResult просто не кажется правильным. Он имеет преимущество в том, что он понятен и прост в использовании / коде. Кроме того, должно быть функциональное решение этого права?

Я предложил 2 других возможных варианта:

  • Оценив, верните (возможно, новый) StreamListener, но это означает, что мне придется сделать Result подтипом StreamListener, который кажется неправильным.

  • Позволяя оценке принимать Stream [Int] в качестве параметра и позволяя StreamListener отвечать за потребление столько Stream, сколько необходимо для определения сбоя или успеха. Проблема, которую я вижу с этим подходом, состоит в том, что класс, который регистрирует слушателей, должен запрашивать каждого слушателя после того, как каждый номер сгенерирован, и предпринимать соответствующие действия сразу после сбоя или успеха. При таком подходе я не вижу, как это могло бы произойти, поскольку каждый слушатель принудительно оценивает поток, пока он не завершит оценку. Здесь нет концепции генерации одного числа.

Есть ли какая-то стандартная идиома Scala / FP, которую я здесь пропускаю?

Ответы [ 2 ]

4 голосов
/ 11 мая 2010

Учитывая ваш первый возможный вариант, я не уверен, почему вы бы сделали Result подтипом StreamListener, а не делали только конкретные подтипы Result, которые относятся к StreamListeners.

sealed trait Result
sealed trait FinalizedResult extends Result

trait StreamListener {
  def evaluate(n: Int): Result
}

case object Uninitialized extends Result with StreamListener {
  def evaluate(n: Int): Result = {
    n match {
      case i if (n == 1) => Succeed
      case i if (n >= 9) => Fail
      case _ => Initialized(n)
    }
  }
}

case class Initialized(target: Int) extends Result with StreamListener {
  def evaluate(n: Int): Result = {
    n match {
      case i if (n == target) => Succeed
      case i if (n == 1) => Fail
      case _ => this
    }
  } 
}

case object Succeed extends FinalizedResult
case object Fail extends FinalizedResult

Но тогда, разве вы не просто перемещаете изменчивость до вызывающего кода, чтобы отследить ссылки на Results / StreamListeners?

2 голосов
/ 11 мая 2010

Я не знаю Скала, но, например, в списках Haskell ленивы и могут представлять «потоки», а «вызов по необходимости» гарантирует, что список / поток оценивается только по мере необходимости (и каждая ячейка только один раз).

В F # в PowerPack есть модуль LazyList, который ведет себя так же. То есть вы можете определить бесконечный поток значений, сопоставить шаблон с ним как список заголовок / отдых, оценить только «по мере необходимости», и разные потребители могут иметь разные «указатели» на него в разных местах (например, неизменный заголовок В остальном списке только эффекты оценки синхронизируются для наиболее продвинутого «указателя» на «границу» оценки).

Так что я думаю, что вам просто нужна правильная «структура данных», как ленивый список, а все остальное выпадает естественным путем. (Примечание: ваша функция как спецификация может застрять в бесконечном цикле (без прерывания), что может быть своего рода эффектом.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...