2011-01-23 4 views
5

정수를 래핑하는 간단한 사례 클래스가 있고 래퍼에 정수를 전달하는 함수를 허용하는 상위 순서 메서드가 있다고 가정합니다.스칼라 케이스 클래스를 추출기로 바꾸면 고차 함수가 손상된 이유는 무엇입니까?

case class Wrapper(x :Int) 
def higherOrder(f : Int => Wrapper) = println(f(42)) 

그런 다음 상위 순서 함수를 호출하여 래퍼의 생성 된 적용 함수를 전달할 수 있습니다. 놀랍게도, 래퍼의 이름을 전달할 수도 있습니다.

higherOrder(Wrapper.apply) // okay 
higherOrder(Wrapper)  // okay, wow! 

정말 멋지다. 우리는 표현 클래스의 추상화를 촉진하는 함수로서 사례 클래스의 이름을 취급 할 수 있습니다. 이 차가움의 예를 보려면 여기에서 답변을 참조하십시오. What does "abstract over" mean?

이제 내 사례 클래스가 충분히 강력하지 않다고 가정하고 대신 추출기를 만들어야합니다. 가볍게 고안된 유스 케이스로서, 정수로 파싱하는 문자열에서 패턴 일치를 할 수 있어야한다고 가정 해 봅시다.

// Replace Wrapper case class with an extractor 
object Wrapper { 
    def apply(x :Int) = new Wrapper(x) 
    def unapply(s :String) :Option[Wrapper] = { 
     // details elided 
    } 
} 
class Wrapper(x :Int) { 
    override def toString = "Wrapper(" + x + ")" 
    // other methods elided 
} 

이러한 변화에 따라, 나는 아직도 내 고차 함수에 Wrapper.apply 전달할 수 없지만 전달은 래퍼가 더 이상 작동합니다.

higherOrder(Wrapper.apply) // still okay 
higherOrder(Wrapper)  // DOES NOT COMPILE 
      ^^^^^^^ 
// type mismatch; found : Wrapper.type (with underlying type Wrapper) 
// required: (Int) => Wrapper 

아야! 여기 왜이 비대칭 성은 문제가되는 것입니다. Odersky, Spoon 및 Venners (Programming in Scala, 500 페이지)의 조언에 따르면

언제나 사례 클래스로 시작한 다음 필요할 경우 추출기로 변경할 수 있습니다. 스칼라에서는 사례 클래스의 추출기 및 패턴에 대한 패턴이 정확히 같기 때문에 클라이언트의 패턴 일치가 계속 작동합니다.

물론 사실이지만, 케이스 클래스 이름을 함수로 사용하면 클라이언트를 중단시킬 것입니다. 그리고 이렇게하면 강력한 추상화가 가능해지기 때문에 확실한 의지가 있습니다.

따라서 상위 순서 함수로 전달할 때 추출기를 사례 클래스와 동일한 방식으로 작동하게하려면 어떻게해야합니까?

+3

질문에 대한 답변을 수락하는 것을 당혹스럽게 생각하지 마십시오. 정말로 사회적으로 받아 들일 만하며, 거의 모든 사람들이 어느 시점에서 그것을 해왔습니다. –

답변

8

스칼라 추출기 컴패니언 개체 컴파일러 후드 컴패니언 객체를 생성하고, 케이스 클래스의 기능을

연장한다. 적용을 포함하여 Wrapper 케이스 클래스에 대한 관련 정적 메소드를 보유합니다. 바이트 코드를 보면서 Wrapper의 동반 객체가 Function1을 확장한다는 것을 알게되었습니다. (실제로는 Autoboxing을 피하기 위해 @specialized를 사용하는 AbstractFunction1을 확장합니다.)

여기에 또한 언급되어 있습니다. Why do case class companion objects extend FunctionN?

래퍼 케이스 클래스를 추출기로 교체 할 때 우리 클라이언트의 이전 버전과의 호환성을 유지하기 위해 AbstractFunction1을 사용하여 자란 동반자 개체를 확장해야합니다.

이것은 소스 코드에서 약간의 조정이됩니다. 적용 메소드는 전혀 변경되지 않습니다. 간단히 말해

object Wrapper extends scala.runtime.AbstractFunction1[Int, Wrapper] { 
+1

이것은'unapply' 추출기와 아무런 관련이 없으므로 이것을 "extractor companion objects"라고 부르는 것은 약간 잘못되었습니다. 올바른 용어는 'factory companion objects'(즉, 새로운 객체를 구성하는 방법으로 '적용'을 구현하는 객체)이어야한다고 생각합니다. –

+0

@Ken, 귀하의 의견에 감사드립니다. 왜 케이스 클래스를 팩토리 컴패니언 객체로 대체해야합니까? 덜 강력한 패턴 매처를 내 문제가 발생하는보다 강력한 패턴 매치로 대체한다는 것은 정확합니다. 내 사례 클래스와 정확히 일치하는 추출기가 필요하므로 내 기존 코드가 손상되지 않습니다. –

+2

당신은 case 클래스를 두 개의 함수가있는 companion 객체로 대체했습니다. 그 중 하나는 팩토리 ('apply' 함수) 였고 다른 하나는 추출기 ('unapply' 함수)였습니다. 비록 당신이이 companion 객체의 extractor aspect를 향상시키는 것이었지만, 우리가 논의하고있는 문제는 factory aspect의 semantics로부터 왔고, companion 객체의'unapply' 함수와 아무 관련이 없습니다. '클래스 C; 객체 C {def apply (x : Int) = ...}'는 같은 문제를 가지고 있습니다. –

12

, Wrapper (객체)는 그것이 적용 방법 단지 객체의 함수가 아닙니다. 이것은 추출 객체가되는 객체와는 전혀 관련이 없습니다.

class Wrapper(val x: Int) { 
    override def toString = "Wrapper(" + x + ")" 
    // other methods elided 
} 

object Wrapper extends (Int => Wrapper) { 
    def apply(x: Int) = new Wrapper(x) 
    def unapply(w: Wrapper): Option[Int] = Some(w.x) 
} 

def higherOrder(f: Int => Wrapper) = println(f(42)) 

가 나는 또한 Wrapperval에 매개 변수를 만들어 매개 변수 주위에 교환 등 대소 수준의 동작을 일치 ​​것이라고 unapply의 당신의 정의에 값을 반환 :

이보십시오.

관련 문제