2013-06-11 5 views
2

나는 내 추출기에서 unapply가 어떤 이유로 두 번 호출된다는 것을 발견했습니다. 누구나 왜 그런지 어떻게 피할 수 있습니까?두 번 호출 된 스칼라 추출기

val data = List("a","b","c","d","e") 

object Uap { 
    def unapply(s:String) = { 
    println("S: "+s) 
    Some(s+"!") 
    }    
} 

println(data.collect{ case Uap(x) => x }) 

이 생산 출력 :

S: a 
S: a 
S: b 
S: b 
S: c 
S: c 
S: d 
S: d 
S: e 
S: e 
List(a!, b!, c!, d!, e!) 

최종 결과는 괜찮하지만 내 실제 프로그램에 적용 취소가 아닌 사소한, 그래서 확실히 두 번 전화를하고 싶지 않아!

답변

6

수집 입력으로 PartialFunction 걸립니다. PartialFunctionisDefinedAtapply의 두 가지 주요 멤버를 정의합니다. collect가 함수를 실행하면 추출기를 한 번 실행하여 함수 isDefinedAt이 특정 입력인지 판별하고, 그렇다면 apply의 일부로 다시 추출기를 실행하여 값을 추출합니다.

isDefinedAt를 올바르게 구현하는 간단한 방법이있는 경우에는 대/소문자 구문 대신 명시 적으로 PartialFunction을 구현하여 직접 구현할 수 있습니다. 또는 당신은

또 다른 옵션은 lift 총 함수의 일부 기능을하는 것 (다음 apply, isDefinedAt를 호출하고있다 수집합니다 본질적으로 어떤 인) 컬렉션의 전체 기능을 가진 filter 다음 map을 할 수 있습니다. PartialFunctionPartialFunction[A,B]A=>Option[B]으로 바꾸는 lift을 정의합니다. 당신은 어떻게이 해제 기능을합니다 (fun 전화) 사용할 수 있습니다 data.map(fun).collect { case Some(x) => x }

+0

또 다른 옵션은 부분 함수 오브젝트의 마지막 값을 캐시하는 것입니다. – ziggystar

+0

@ 그렉 이것은 사실이지만, 올드 스쿨의 일종으로, 2.11에서는 사실이 아니고 2.10에서 다소 괜찮은 수정을 가졌습니다. –

-1

대신 map를 사용할 수 있습니다

scala> println(data.map{ case Uap(x) => x }) 
S: a 
S: b 
S: c 
S: d 
S: e 
List(a!, b!, c!, d!, e!) 

가 왜 작동 이유를 모른다.

+0

내가 할 수 있다고 생각하지 마십시오. 내 실제 응용 프로그램에서 나는 실제로 선택 취소 로직에서 선택 로직을 수행하므로 항상 일부 (무언가)를 반환하지는 않습니다. 지도에는 항상 1 대 1 결과가 필요합니다. – Greg

+1

@Greg보다 'flatMap'을 사용하십시오. – Jakozaur

+0

OP 패턴 매치가 부분적이지 않기 때문에 투표를 잘못했다고 생각합니다. 사용 사례가 중요합니다. –

4

사실,이 성능 버그 2.11에서 해결되었습니다

$ skala 
Welcome to Scala version 2.11.0-20130423-194141-5ec9dbd6a9 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_06). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> val data = List("a","b","c","d","e") 
data: List[String] = List(a, b, c, d, e) 

scala> 

scala> object Uap { 
    | def unapply(s:String) = { 
    |  println("S: "+s) 
    |  Some(s+"!") 
    | }    
    | } 
defined object Uap 

scala> 

scala> println(data.collect{ case Uap(x) => x }) 
S: a 
S: b 
S: c 
S: d 
S: e 
List(a!, b!, c!, d!, e!) 

applyOrElse에 효율성 노트를 참조하십시오. 여기

이 문제를 쉽게 확장하여 구제되어 2.10 대한 버전이다 :

object Test extends App { 
    import scala.collection.TraversableLike 
    import scala.collection.generic.CanBuildFrom 
    import scala.collection.immutable.StringLike 

    implicit class Collector[A, Repr, C <: TraversableLike[A, Repr]](val c: C) extends AnyVal { 
    def collecting[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { 
     val b = bf(c.repr) 
     c.foreach(pf.runWith(b += _)) 
     b.result 
    } 
    } 

    val data = List("a","b","c","d","e") 

    object Uap { 
    def unapply(s:String) = { 
     println("S: "+s) 
     s match { 
     case "foo" => None 
     case _  => Some(s+"!") 
     } 
    } 
    } 
    val c = Collector[String, List[String], List[String]](data) 
    Console println c.collecting { case Uap(x) => x } 
} 

결과 : UAP이 버전의 부분임을

$ scalac -version 
Scala compiler version 2.10.1 -- Copyright 2002-2013, LAMP/EPFL 

[email protected] ~/tmp 
$ scalac applyorelse.scala ; scala applyorelse.Test 
S: a 
S: b 
S: c 
S: d 
S: e 
List(a!, b!, c!, d!, e!) 

참고

scala> val data = List("a","b","c","d","e", "foo") 
data: List[String] = List(a, b, c, d, e, foo) 

scala> data.map{ case Uap(x) => x } 
S: a 
S: b 
S: c 
S: d 
S: e 
S: foo 
scala.MatchError: foo (of class java.lang.String) 

유스 케이스가 PF 인 경우 코드가 부분적이어야한다고 생각합니다.

def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { 
val b = bf(repr) 
for (x <- this) if (pf.isDefinedAt(x)) b += pf(x) 
b.result 
} 

그것은 pf.isDefinedAt(x)을 사용으로 @stew 대답에 추가

1

collect이 구현됩니다. scalac -Xprint:typer check.scala (check.scala에 코드가 들어 있습니다).다음과 같이 인쇄합니다.

.... 
final def isDefinedAt(x1: String): Boolean = ((x1.asInstanceOf[String]:String): String @unchecked) match { 
     case check.this.Uap.unapply(<unapply-selector>) <unapply> ((x @ _)) => true 
     case (defaultCase$ @ _) => false 
    } 

그래서 여기에서 unapply을 다시 호출합니다. 이것은 두 번 인쇄되는 이유, 즉 정의 된 경우 한 번 확인한 다음`pf (x)에서 이미 호출 된 경우 다음에 인쇄하는 이유를 설명합니다.

@ som-snytt이 맞습니다. 스칼라 2.11으로 TraversableLike 함수가 변경된다 수집 : 한번만 인쇄 이유는 내부적으로는이 정의 된 경우 확인하는 applyOrElse을 호출한다는 것이다

def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { 
val b = bf(repr) 
foreach(pf.runWith(b += _)) 
b.result 
} 

. 그렇다면 거기에 기능을 적용합니다 (위의 경우 (b += _)). 따라서 한 번만 인쇄됩니다.