할당 된 값이 아닌 단위로 평가되는 Scala 할당의 동기는 무엇입니까?


84

할당 된 값이 아닌 단위로 평가되는 Scala 할당의 동기는 무엇입니까?

I / O 프로그래밍의 일반적인 패턴은 다음과 같은 작업을 수행하는 것입니다.

while ((bytesRead = in.read(buffer)) != -1) { ...

하지만 스칼라에서는 불가능합니다.

bytesRead = in.read(buffer)

..은 새로운 bytesRead 값이 아닌 Unit을 반환합니다.

기능적 언어에서 빼놓을 수있는 흥미로운 것 같습니다. 왜 그렇게되었는지 궁금합니다.


David Pollack은 Martin Odersky가 자신의 답변에 남긴 댓글에 의해 거의 승인 된 몇 가지 직접적인 정보를 게시했습니다. 명태의 대답을 안전하게 받아 들일 수 있다고 생각합니다.
Daniel C. Sobral

답변:


88

나는 할당이 단위가 아닌 할당 된 값을 반환하도록 옹호했습니다. Martin과 나는 이에 대해 앞뒤로 갔지만 그의 주장은 시간의 95 %를 뽑아 내기 위해 스택에 값을 두는 것은 바이트 코드 낭비이며 성능에 부정적인 영향을 미친다는 것이 었습니다.


7
Scala 컴파일러가 할당 값이 실제로 사용되는지 여부를 확인하지 못하고 이에 따라 효율적인 바이트 코드를 생성 할 수없는 이유가 있습니까?
Matt R

43
세터가 있으면 그렇게 쉽지 않습니다. 모든 세터는 결과를 반환해야하므로 작성하기가 어렵습니다. 그런 다음 컴파일러는이를 최적화해야하므로 호출간에 수행하기가 어렵습니다.
Martin Odersky 2010 년

1
귀하의 주장은 의미가 있지만 Java 및 C #은 그것에 반대합니다. 생성 된 바이트 코드로 이상한 일을하고 있다고 생각합니다. 그러면 Scala의 할당이 클래스 파일로 컴파일되고 다시 Java로 디 컴파일되는 것처럼 보일까요?
Phương Nguyễn

3
@ PhươngNguyễn 차이점은 Uniform Access Principle입니다. C # / Java setter (일반적으로)에서 void. Scala에서 할당이 수행되면 foo_=(v: Foo)반환 Foo해야합니다.
Alexey Romanov 2011

5
@Martin Odersky : 다음은 어떻습니까 : setter가 남아 있고 void( Unit) 할당 은 다음과 x = value동일하게 변환됩니다 x.set(value);x.get(value). 컴파일러는 최적화 단계 get에서 값이 사용되지 않은 경우-호출을 제거 합니다. 이전 버전과의 비 호환성으로 인한 새로운 주요 변경 사항이 될 수 있으며 사용자에 대한 짜증이 적을 수 있습니다. 어떻게 생각해?
Eugen Labun 2012

20

나는 실제 이유에 대한 내부 정보에 대해 잘 알지 못하지만 의심은 매우 간단합니다. Scala는 프로그래머가 자연스럽게 for-comprehension을 선호하도록 부작용 루프를 사용하기 어렵게 만듭니다.

여러 가지 방법으로이를 수행합니다. 예를 들어, for변수를 선언하고 변경하는 루프 가 없습니다 . while조건을 테스트하는 동시에 루프에서 상태를 (쉽게) 변경할 수 없습니다. 즉, 바로 앞과 끝에서 돌연변이를 반복해야하는 경우가 많습니다. while블록 내에서 선언 된 변수는 while테스트 조건 에서 볼 수 없으므로 do { ... } while (...)훨씬 유용 하지 않습니다 . 등등.

해결 방법 :

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

가치가 무엇이든간에.

대체 설명으로 Martin Odersky는 그러한 사용으로 인해 발생하는 몇 가지 매우 추악한 버그에 직면해야했으며 그의 언어에서이를 금지하기로 결정했습니다.

편집하다

David Pollack 은 몇 가지 실제 사실로 답변 했으며 Martin Odersky 자신이 자신의 답변에 대해 언급 한 사실에 의해 분명하게 입증 되었으며 Pollack이 제기 한 성능 관련 문제에 대한 신뢰를 제공합니다.


3
그래서 아마도 for루프 버전은 다음과 같습니다 for (bytesRead <- in.read(buffer) if (bytesRead) != -1더 거기에 있기 때문에 작동하지 않습니다 것을 제외하고 위대없는 foreachwithFilter사용할 수!
oxbow_lakes

12

이것은보다 "공식적으로 올바른"유형 시스템을 갖는 Scala의 일부로 발생했습니다. 공식적으로 말하면 할당은 순전히 부작용이있는 진술이므로을 반환해야합니다 Unit. 이것은 좋은 결과를 가져옵니다. 예를 들면 :

class MyBean {
  private var internalState: String = _

  def state = internalState

  def state_=(state: String) = internalState = state
}

state_=메소드가 리턴 Unit(같은 세터 예상되는) 정확하게 할당 반환하기 때문에 Unit.

스트림 복사와 같은 C 스타일 패턴의 경우이 특정 디자인 결정이 약간 번거로울 수 있다는 데 동의합니다. 그러나 실제로는 일반적으로 비교적 문제가 없으며 실제로 유형 시스템의 전반적인 일관성에 기여합니다.


고마워, 다니엘. 일관성이 할당과 세터가 모두 값을 반환했다면 더 좋을 것 같습니다! (그들이 할 수없는 이유가 없습니다.) 나는 아직 "순수한 부작용 진술"과 같은 개념의 뉘앙스를 괴롭히지 않는 것 같습니다.
Graham Lea

2
@Graham :하지만 일관성을 따르고 모든 setter가 복잡하더라도 설정 한 값을 반환하는지 확인해야합니다. 이것은 어떤 경우에는 복잡하고 다른 경우에는 잘못된 것 같습니다. (오류가 발생하면 무엇을 반환합니까? null? – 아닙니다. None? – 그러면 Type [T]이됩니다.) 그와 일치하는 것이 어렵다고 생각합니다.
Debilski

7

아마도 이것은 명령 쿼리 분리 원칙 때문일까요?

CQS는 OO와 함수형 프로그래밍 스타일의 교차점에서 인기있는 경향이 있는데, 이는 부작용이 있거나없는 (즉, 객체를 변경하는) 객체 메소드간에 명백한 차이를 생성하기 때문입니다. 변수 할당에 CQS를 적용하면 평소보다 더 많이 걸리지 만 동일한 아이디어가 적용됩니다.

CQS가 유용한 이유의 짧은 그림 : 기호가있는 가상 하이브리드 F / OO 언어 고려 List방법이 클래스 Sort, Append, First,와 Length. 명령형 OO 스타일에서는 다음과 같은 함수를 작성할 수 있습니다.

func foo(x):
    var list = new List(4, -2, 3, 1)
    list.Append(x)
    list.Sort()
    # list now holds a sorted, five-element list
    var smallest = list.First()
    return smallest + list.Length()

보다 기능적인 스타일에서는 다음과 같이 작성할 가능성이 높습니다.

func bar(x):
    var list = new List(4, -2, 3, 1)
    var smallest = list.Append(x).Sort().First()
    # list still holds an unsorted, four-element list
    return smallest + list.Length()

이것들은 똑같은 일을 하려고 하는 것 같지만 , 분명히 둘 중 하나가 틀렸고, 메소드의 동작에 대해 더 많이 알지 못한다면 어떤 것을 말할 수 없습니다.

CQS를 사용하지만, 우리는 경우에 주장 할 AppendSort따라서 때 우리가해서는 안 두 번째 양식을 사용하여 버그를 만들기에서 우리를 방지 목록을 변경, 그들은 단위 유형을 반환해야합니다. 따라서 부작용의 존재는 메서드 시그니처에도 암시 적으로 포함됩니다.


4

나는 이것이 프로그램 / 언어에 부작용이 없도록 유지하기위한 것이라고 생각합니다.

당신이 설명하는 것은 일반적으로 나쁜 것으로 간주되는 부작용의 의도적 인 사용입니다.


헤. 부작용이없는 스칼라? :) 또한, val a = b = 1( val앞의 "마법" 을 상상해보십시오 b) 대 val a = 1; val b = 1;.

이것은 적어도 여기에 설명 된 의미에서는 부작용과 관련이 없습니다. 부작용 (컴퓨터 과학)
Feuermurmel

4

부울 표현식으로 할당을 사용하는 것은 가장 좋은 스타일이 아닙니다. 동시에 두 가지 작업을 수행하면 종종 오류가 발생합니다. 그리고 "=="대신 "="의 우발적 인 사용은 Scalas 제한으로 방지됩니다.


2
나는 이것이 쓰레기 이유라고 생각합니다! OP가 게시 한대로 코드는 여전히 컴파일되고 실행됩니다. 합리적으로 예상 할 수있는 작업을 수행하지 않습니다. 한 번만 더하면됩니다!
oxbow_lakes

1
if (a = b)와 같이 작성하면 컴파일되지 않습니다. 따라서 최소한이 오류는 피할 수 있습니다.
deamon

1
OP는 '=='대신 '='를 사용하지 않았으며 둘 다 사용했습니다. 그는 할당이 다른 값과 비교하는 데 사용할 수있는 값을 반환 할 것으로 예상합니다 (예 : 1)
IttayD

@deamon : a와 b가 부울이면 (적어도 Java에서) 컴파일됩니다. 나는 if (a = true)를 사용하여이 함정에 빠지는 초보자를 보았다. 더 간단한 if (a)를 선호하는 또 하나의 이유 (더 중요한 이름을 사용하면 더 명확합니다!).
PhiLho

2

그건 그렇고 : 나는 자바에서도 초기 while-trick 어리 석음을 발견합니다. 왜 이런 식으로하지 않습니까?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
   //do something 
}

물론 할당은 두 번 나타나지만 최소한 bytesRead는 그것이 속한 범위에 있으며 재미있는 할당 트릭을 가지고 노는 것이 아닙니다 ...


1
속임수는 매우 일반적인 방법이지만 일반적으로 버퍼를 통해 읽는 모든 앱에 나타납니다. 그리고 항상 OP 버전처럼 보입니다.
TWiStErRob

0

간접 참조 유형이있는 한 이에 대한 해결 방법을 가질 수 있습니다. 순진한 구현에서는 임의의 유형에 대해 다음을 사용할 수 있습니다.

case class Ref[T](var value: T) {
  def := (newval: => T)(pred: T => Boolean): Boolean = {
    this.value = newval
    pred(this.value)
  }
}

그런 다음 ref.value나중에 참조에 액세스하는 데 사용해야하는 제약 조건 하에서 조건자를 다음 while과 같이 작성할 수 있습니다.

val bytesRead = Ref(0) // maybe there is a way to get rid of this line

while ((bytesRead := in.read(buffer)) (_ != -1)) { // ...
  println(bytesRead.value)
}

입력 할 필요 bytesRead없이보다 암시적인 방식으로 검사를 수행 할 수 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.