Scala 특성에서 val 또는 def를 언제 사용합니까?


90

나는 통해가는 효과적인 스칼라 슬라이드 과 사용 결코 슬라이드 (10)에 언급 valA의 trait추상적 인 회원들과 사용을 위해 def대신. 이 슬라이드는 왜 추상 val을 사용하는 trait것이 반 패턴 인지에 대해서는 자세히 언급하지 않습니다 . 누군가가 추상 방법에 대한 특성에서 val 대 def를 사용하는 것에 대한 모범 사례를 설명 할 수 있다면 감사하겠습니다.

답변:


130

A defdef, a val, a lazy val또는 object. 그래서 이것은 멤버를 정의하는 가장 추상적 인 형태입니다. 트레이 트는 일반적으로 추상적 인 인터페이스이기 때문에 당신이 원한다고 val말하는 것은 구현이 어떻게해야 하는지를 말하는 것입니다. 을 요청 val하면 구현 클래스는 def.

A val는 안정적인 식별자가 필요한 경우에만 필요합니다 (예 : 경로 종속 유형). 그것은 일반적으로 필요하지 않은 것입니다.


비교:

trait Foo { def bar: Int }

object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok

class F2(val bar: Int) extends Foo // ok

object F3 extends Foo {
  lazy val bar = { // ok
    Thread.sleep(5000)  // really heavy number crunching
    42
  }
}

만약 당신이

trait Foo { val bar: Int }

F1또는 을 정의 할 수 없습니다 F3.


좋습니다. 혼란스럽고 @ om-nom-nom으로 대답합니다. abstract를 사용 val하면 초기화 문제가 발생할 수 있습니다.

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

이것은 내 개인적인 의견으로는 컴파일러에서 수정하여 향후 Scala 버전에서 사라져야하는 추악한 문제이지만, 현재 이것은 추상을 사용해서는 안되는 이유이기도합니다. val .

편집 (2016 년 1 월) : 구현으로 추상 val선언 을 재정의 lazy val할 수 있으므로 초기화 실패도 방지 할 수 있습니다.


8
까다로운 초기화 순서와 놀라운 null에 대한 단어?
om-nom-nom

그래 ... 난 거기도 가지 않을거야. 사실 이것들도 val에 대한 주장이지만, 기본 동기는 구현을 숨기는 것이어야한다고 생각합니다.
0__

2
이것은 최근의 스칼라 버전 (이 댓글의 같은 2.11.4)에서 변경 될 수 있습니다,하지만 당신은 대체 할 수 있습니다 valA를을 lazy val. 귀하의 주장은 당신이 만들 수없는 것이라고 F3하면 barA가 있었다 val올바르지 않습니다. 즉, def
트레이 트

푸 / 실패의 예는 대체 할 경우 예상대로 작동 val schoko = bar + bar으로 lazy val schoko = bar + bar. 이것이 초기화 순서를 제어하는 ​​한 가지 방법입니다. 또한 파생 클래스에서 lazy val대신 사용 def하면 재 계산이 방지됩니다.
Adrian

2
로 변경 val bar: Int하면 def bar: Int Fail.schoko여전히 0입니다.
Jasper-M

8

나는 valval 선언이 명확하지 않고 직관적이지 않은 초기화 순서를 가지고 있기 때문에 특성에 사용하지 않는 것을 선호합니다 . 이미 작동하는 계층 구조에 특성을 추가하면 이전에 작동했던 모든 것을 깨뜨릴 수 있습니다. 내 주제를 참조하십시오. 최종 클래스가 아닌 클래스에서 일반 val을 사용하는 이유

이 val 선언 사용에 대한 모든 것을 염두에 두어야 결국 오류가 발생합니다.


더 복잡한 예제로 업데이트

그러나 사용을 피할 수없는 경우가 있습니다 val. @ 0__이 언급했듯이 때때로 안정적인 식별자가 필요하며 def하나가 아닙니다.

나는 그가 말한 것을 보여주는 예를 제공 할 것입니다.

trait Holder {
  type Inner
  val init : Inner
}
class Access(val holder : Holder) {
  val access : holder.Inner =
    holder.init
}
trait Access2 {
  def holder : Holder
  def access : holder.Inner =
    holder.init
}

이 코드는 오류를 생성합니다.

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

잠시 시간을내어 컴파일러가 불평 할 이유가 있다는 것을 이해할 수있을 것이라고 생각한다면. 의 Access2.access경우는 어떤 방법으로 파생 반환 형식은 할 수 없습니다. def holder광범위한 방식으로 구현 될 수 있음을 의미합니다. 각 호출에 대해 서로 다른 보유자를 반환 할 수 있으며 보유자는 서로 다른 Inner유형을 통합 합니다. 그러나 Java 가상 머신은 동일한 유형이 리턴 될 것으로 예상합니다.


3
초기화 순서는 중요하지 않지만 대신에 안티 패턴에 비해 런타임 동안 놀라운 NPE가 발생합니다.
Jonathan Neufeld

scala에는 명령 적 성격을 숨기는 선언적 구문이 있습니다. 때로는 imperativeness는 반 직관적 인 작동
ayvango

-4

다음과 같은 것이 작동하지 않기 때문에 항상 def를 사용하는 것이 약간 어색해 보입니다.

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

다음과 같은 오류가 발생합니다.

error: value id_= is not a member of Entity

2
관련이 없습니다. def 대신 val을 사용하면 오류가 발생합니다 (오류 : val에 재 할당). 그것은 완벽하게 논리적입니다.
volia17

당신은 사용하지 않을 경우 var. 요점은 그들이 필드라면 그 자체로 지정되어야한다는 것입니다. 나는 단지 모든 것을 def근시 라고 생각 합니다.
Dimitry

@Dimitry, 확실히 사용 var하면 캡슐화를 깰 수 있습니다. 그러나 a def(또는 a val)를 사용하는 것이 전역 변수보다 선호됩니다. 나는 당신이 찾고있는 것은 같은 생각 case class ConcreteEntity(override val id: Int) extends Entity에서 당신이 그것을 만들 수 있도록 def create(e: Entity) = ConcreteEntity(1)이 캡슐을 깨고 모든 클래스는 엔터티를 변경할 수 있도록보다 안전합니다.
Jono
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.