2016-11-30 4 views
2

При попытке понять объекты-компаньоны я написал следующий код, который подсчитывает количество экземпляров класса. Мне пришлось использовать «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 
+0

Функциональные или государственные Монады кажутся подходящими – naomik

ответ

3

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

В частности, это означает, что в чисто функциональный параметр, каждый вызов 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) 

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

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

1

Это не то, что 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 
2

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

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 

Примечание: государство здесь происходит от проекта «Кошки».

+0

Нравится это ваше состояние 'Unit', которое не имеет большого смысла. Я думаю, вы хотите вместо этого использовать 'State [Int, Unit ]' и использовать в функции' countCalls' нечто вроде 'State.modify (_ + 1)'? –

+0

Имеет смысл использовать государственную монаду, как вы описали (я обновил свой ответ). – soote