Как реализовать счетчик в функциональном программировании

Пытаясь понять сопутствующие объекты, я написал следующий код, который подсчитывает количество экземпляров класса. Мне пришлось использовать «var», чтобы вести счет. Есть ли способ «функционального программирования» для достижения той же задачи, т.е. использовать неизменяемые переменные.

class C {
  C.counter+=1
  def someCFunction = {println ("some C function. Counter is "+C.counter)}
}

object C{
  var counter:Int=0 //I do not want to use var
}

val c1 = new C
c1.someCFunction

val c2 = new C
c2.someCFunction

person Manu Chadha    schedule 30.11.2016    source источник
comment
Функторы или State Monad кажутся подходящими   -  person Mulan    schedule 01.12.2016


Ответы (3)


Это хороший пример использования State Monad. Вместо того, чтобы изменять переменную на месте, вы создаете новое значение и передаете его.

import cats.data.State
class C {}
object C { val counter: State[Int, Unit] = State.pure() }

def createNewC: State[Int, C] = {
  // increment the count, and return a new instance of C
  C.counter.modify(_ + 1).map(_ => new C)
}

val countAll = for {
  c0 <- createNewC
  c1 <- createNewC
  c2 <- createNewC
  c3 <- createNewC
} yield {
  // use your instance of C in here
  ()
}

// actually run your program, start the counter at 0
countAll.run(0).value._1 // 4

Примечание. Состояние здесь взято из проекта Cats.

person soote    schedule 30.11.2016
comment
Таким образом, ваше состояние Unit не имеет особого смысла. Я думаю, вы хотите вместо этого использовать State[Int, Unit] и использовать что-то вроде State.modify(_ + 1) в функции countCalls? - person Peter Neyens; 30.11.2016

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

В частности, это означает, что в чисто функциональной настройке каждый вызов new C будет возвращать полностью идентичный объект-счетчик. Обычно это хорошо, потому что так легче рассуждать о вашей программе, но это как бы мешает тому, что вы пытаетесь там сделать. Чтобы сделать объекты C разными, вам нужно явно передать значения их счетчиков, что, честно говоря, просто заметает проблему под ковер.

val c1 = new C(0)
val c2 = new C(1)

Если вы хотите иметь глобальную переменную «счетчик», такую ​​​​как переменная внутреннего класса, которую вы использовали, один из возможных способов реализовать ее в чисто функциональной настройке - передать значение счетчика каждой функции, которой нужен счетчик, и чтобы эти функции также возвращали обновленная версия счетчика. Для краткого примера:

def increment_counter(n: Int): Int = { n + 1)

def create_c(n: Int): (C, Int) = {
    val c = new C(n)
    val n' = increment_counter n
    (c, n')
}

val n = 0
val (c1, n') = create_c(n)
val (c2, n'') = create_c(n')
val n' = increment_counter(n)

Вы можете структурировать это немного лучше с помощью шаблона State Monad (в большинстве случаев знакомства с монадами он будет, вероятно, использоваться в качестве примера).

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

person hugomg    schedule 30.11.2016

Дело не в том, что var по своей природе плохо. Это на языке по какой-то причине. Просто того, что он представляет, объекта, который поддерживает некоторую форму изменчивого состояния, следует избегать, когда это возможно. Когда этого нельзя избежать, если дизайн требует промежуточного количества экземпляров класса, тогда его область действия должна быть максимально ограничена.

class C private {  // private constructor, can only use factory method
  def someCFunction = {println ("some C function. Counter is "+ C.getCount())}
}
object C{
  private[this] var counter:Int = 0    // not even companions can see this
  def apply() = {counter += 1; new C}  // factory method
  def getCount() = counter             // accessor method
}

val c1 = C()
c1.someCFunction

val c2 = C()
c2.someCFunction
person jwvh    schedule 30.11.2016