2016-11-30 9 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

FunctorsまたはState Monadが適切であるようです – naomik

答えて

3

つの発現を評価すること値だけ表現自体に依存することです。これは、評価される順序(左から右、右から左、厳密、怠惰)、オペレーティングシステムの状態、時刻などに依存しません。

特に、これは純粋にnew Cを呼び出すたびに完全に同一のカウンタオブジェクトが返されます。これは通常、あなたのプログラムについて簡単に推論することができるので良いことですが、そこで何をしようとしているのかが分かります。 Cオブジェクトを異なるものにするには、カウンタ値を明示的に渡す必要があります。これは正直言って、敷物の下で問題を掃除するだけです。

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

あなたが必要とするすべての関数にカウンタ値を渡すことであろう純粋に機能的な設定でそれを実装する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

これは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プロジェクトからのものです。

+0

このように、あなたの状態は意味をなさない 'Unit'です。私は 'State [Int、Unit]'を代わりに使い、 'countCalls'関数で' State.modify(_ + 1) 'のようなものを使用したいと思いますか? –

+0

説明したように、状態のモナドを使うほうが意味があります(私は答えを更新しました)。 – soote

関連する問題