Scala에서 val-mutable 대 var-immutable


97

변경 가능한 컬렉션에서 val을 사용하는 경우와 변경 불가능한 컬렉션에서 var를 사용하는 경우에 대한 Scala의 지침이 있습니까? 아니면 정말 불변의 컬렉션으로 val을 목표로해야할까요?

두 가지 유형의 컬렉션이 모두 있다는 사실은 저에게 많은 선택권을 제공하며 종종 그 선택을하는 방법을 모릅니다.


답변:


104

꽤 일반적인 질문입니다. 어려운 것은 중복을 찾는 것입니다.

참조 투명성을 위해 노력해야합니다 . 즉, "e"라는 표현이 있으면를 만들고으로 val x = e바꿀 수 e있습니다 x. 이것은 가변성이 깨지는 속성입니다. 디자인 결정을 내려야 할 때마다 참조 투명성을 최대화하십시오.

실질적으로 메서드 로컬 var은 메서드를 벗어나지 var않기 때문에 존재 하는 가장 안전 합니다. 방법이 짧으면 더 좋습니다. 그렇지 않은 경우 다른 방법을 추출하여 줄이십시오.

반면에 변경 가능한 컬렉션은 그렇지 않더라도 탈출 할 가능성 이 있습니다. 코드를 변경할 때 다른 메서드에 전달하거나 반환 할 수 있습니다. 그것은 참조 투명성을 깨뜨리는 종류의 것입니다.

객체 (필드)에서도 거의 똑같은 일이 발생하지만 더 심각한 결과가 발생합니다. 어느 쪽이든 객체는 상태를 가지므로 참조 투명성이 깨집니다. 그러나 변경 가능한 컬렉션이 있다는 것은 객체 자체조차도 누가 변경하는지 제어 할 수 없다는 것을 의미합니다.


38
니스, 내 마음에 새로운 큰 그림 : 선호 immutable val이상 immutable var이상 mutable val이상 mutable var. 특히 immutable var끝났습니다 mutable val!
피터 슈미츠

2
(변경할 수있는 부작용이있는 "함수"를 유출하는 것과 같이) 로컬 mutable을 통해 여전히 닫을 수 있습니다 var. 변경 불가능한 컬렉션을 사용하는 또 다른 좋은 기능은 var변경된 경우에도 오래된 복사본을 효율적으로 보관할 수 있다는 것 입니다.
신비한 댄

1
tl; dr : 다른 함수로 전달 하는 경우 var x: Set[Int] 보다 선호 합니다. 전자의 경우에는 해당 함수가 사용자를 위해 변경 될 수 없습니다 . val x: mutable.Set[Int]xx
pathikrit 2015 년

17

변경 불가능한 컬렉션으로 작업하고이를 "수정"해야하는 경우 (예 : 루프에 요소를 추가하는 var경우) 결과 컬렉션을 어딘가에 저장해야하므로 s 를 사용해야 합니다. 변경 불가능한 컬렉션에서만 읽는 경우 vals 를 사용하십시오 .

일반적으로 참조와 객체를 혼동하지 않도록하십시오. vals는 불변 참조 (C의 상수 포인터)입니다. 당신이 사용하는 경우 즉, val x = new MutableFoo()당신은 할 수 있습니다 개체 변경x포인트를,하지만 당신은 할 수 없습니다 객체로 변경하는 x 점. 를 사용하면 반대가 유지됩니다 var x = new ImmutableFoo(). 내 초기 조언을 따름 : 참조가 가리키는 객체를 변경할 필요가 없으면 vals를 사용하십시오 .


1
var immutable = something(); immutable = immutable.update(x)불변 컬렉션을 사용하는 목적을 무효화합니다. 이미 참조 투명성을 포기했으며 일반적으로 더 나은 시간 복잡성으로 변경 가능한 컬렉션에서 동일한 효과를 얻을 수 있습니다. 네 가지 가능성 ( valvar, 변경 가능 및 불변) 중 이것은 가장 의미가 없습니다. 나는 자주 사용 val mutable합니다.
Jim Pivarski 2013 년

3
@JimPivarski 나는 다른 사람들과 마찬가지로 동의하지 않습니다. Daniel의 대답과 Peter의 의견을 참조하십시오. 데이터 구조를 업데이트해야하는 경우 변경 가능한 val 대신 변경 불가능한 var를 사용하면 로컬 가정을 깨는 방식으로 다른 사람에 의해 수정 될 위험없이 구조에 대한 참조를 누출 할 수 있다는 이점이 있습니다. 이러한 "기타"의 단점은 오래된 데이터를 읽을 수 있다는 것입니다.
Malte Schwerhoff

나는 내 마음을 바꾸었고 나는 당신과 동의합니다 (나는 역사에 대한 나의 원래 코멘트를 남길 것입니다). 나는 그 이후로 이것을 사용했고, 특히 var list: List[X] = Nil; list = item :: list; ...내가 한때 다르게 쓴 것을 잊었다.
Jim Pivarski 2013

@MalteSchwerhoff : 일관성이 중요하다면 프로그램을 어떻게 설계했는지에 따라 "부실 데이터"가 실제로 바람직합니다. 이것은 예를 들어 Clojure에서 동시성이 작동하는 방식의 주요 기본 원칙 중 하나입니다.
Erik Kaplun 2014 년

@ErikAllik 부실 데이터가 그 자체로 바람직하다고 말하지는 않지만 고객에게 제공하려는 / 필요한 보증에 따라 완벽하게 괜찮을 수 있다는 데 동의합니다. 아니면 부실 데이터를 읽는 유일한 사실이 실제로 이점이되는 예가 있습니까? 더 나은 성능이나 더 간단한 API가 될 수있는 오래된 데이터를 받아 들인 결과를 의미하지는 않습니다.
Malte Schwerhoff

9

이에 답하는 가장 좋은 방법은 예를 사용하는 것입니다. 어떤 이유로 단순히 숫자를 수집하는 프로세스가 있다고 가정합니다. 이 번호를 기록하고 이를 수행 하기 위해 컬렉션을 다른 프로세스로 보냅니다 .

물론 수집 물을 로거로 보낸 후에도 여전히 숫자를 수집하고 있습니다. 그리고 실제 로깅을 지연시키는 로깅 프로세스에 약간의 오버 헤드가 있다고 가정 해 보겠습니다. 이것이 어디로 가는지 알 수 있기를 바랍니다.

이 컬렉션을 mutable에 저장하면 val(계속 추가하고 있기 때문에 변경 가능) 이는 로깅을 수행하는 프로세스가 컬렉션 프로세스에 의해 여전히 업데이트되고 있는 동일한 객체 를 보게 될 것임을 의미합니다 . 해당 컬렉션은 언제든지 업데이트 될 수 있으므로 로깅 할 때 실제로 보낸 컬렉션을 로깅하지 않을 수 있습니다.

immutable을 사용하는 경우 var변경 불가능한 데이터 구조를 로거에 보냅니다. 우리가 우리의 컬렉션에 더 많은 번호를 추가 할 때, 우리는 것입니다 대체 우리를 var로모그래퍼 새로운 불변의 데이터 구조 . 이것은 로거로 전송 된 컬렉션이 교체된다는 의미가 아닙니다! 전송 된 컬렉션을 여전히 참조하고 있습니다. 따라서 로거는 실제로받은 컬렉션을 기록합니다.



-2

var immutableval mutable

이 질문에 대한 많은 훌륭한 답변 외에도. 다음은 잠재적 인 위험을 보여주는 간단한 예입니다 val mutable.

변경 가능한 개체는 메서드 내에서 수정할 수 있으며,이를 매개 변수로 사용하지만 재 할당은 허용되지 않습니다.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

결과:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

var immutable재 할당이 허용되지 않기 때문에 에서는 이와 같은 일이 발생할 수 없습니다 .

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

결과 :

error: reassignment to val

함수 매개 변수는 val이 재 할당이 허용되지 않는 것으로 취급되기 때문에 .


이것은 올바르지 않습니다. 그 오류가 발생한 이유는 기본적으로 변경 불가능한 두 번째 예제에서 Vector를 사용했기 때문입니다. ArrayBuffer를 사용하면 잘 컴파일되고 새 요소를 추가하고 변경된 버퍼를 출력하는 동일한 작업을 수행하는 것을 볼 수 있습니다. pastebin.com/vfq7ytaD
EdgeCaseBerg

@EdgeCaseBerg, 두 번째 예제에서는 의도적으로 Vector를 사용하고 있습니다. 첫 번째 예제의 동작 mutable valimmutable var. 여기에 무엇이 잘못 되었습니까?
Akavall

여기에서 사과와 오렌지를 비교하고 있습니다. 벡터에는 +=배열 버퍼와 같은 방법 이 없습니다 . 귀하의 답변 +=x = x + y그것이 아닌 것과 동일 함을 의미합니다 . 함수 매개 변수가 vals로 취급된다는 귀하의 진술은 정확하고 언급 한 오류가 발생하지만 =. ArrayBuffer로 동일한 오류를 얻을 수 있으므로 여기의 컬렉션 변경 가능성은 실제로 관련이 없습니다. 따라서 OP가 말하는 것을 얻지 못하기 때문에 좋은 대답이 아닙니다. 의도하지 않은 경우 변경 가능한 컬렉션을 전달하는 위험에 대한 좋은 예입니다.
EdgeCaseBerg

@EdgeCaseBerg하지만을 ArrayBuffer사용하여 내가 얻는 동작을 복제 할 수 없습니다 Vector. OP의 질문은 광범위하지만 언제 사용할 지에 대한 제안을 찾고 있었으므로 변경 가능한 컬렉션을 전달하는 위험을 보여주기 때문에 내 대답이 유용하다고 생각합니다 (그것이 val도움 이 되지 않습니다). immutable var보다 안전합니다 mutable val.
Akavall
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.