2014-11-19 2 views
1

참고 : 나와 동거하십시오. equals를 재정의하는 방법이나 부동 소수점 값을 비교하는 사용자 지정 메서드를 만드는 방법을 묻지 않습니다.스칼라의 부동 소수점 값에 대한 우선 적용 재정의

스칼라는 객체를 값으로 비교할 수 있고 작은 코드로 그렇게 할 수있는 일련의 도구를 제공함으로써 매우 뛰어납니다. 특히 사례 클래스, 튜플 및 전체 컬렉션 비교를 허용합니다.

나는 종종 집중적 인 계산을 수행하고 돌아 가기 위해 간단한 데이터 구조를 생성하는 메소드를 호출했으며, 특정 입력이 주어진 메소드를 호출 한 다음 결과를 하드 코딩 된 값과 비교하는 단위 테스트를 작성할 수 있습니다. . 예를 들어 :

def compute() = 
{ 
    // do a lot of computations here to produce the set below... 
    Set(('a', 1), ('b', 3)) 
} 

val A = compute() 
val equal = A == Set(('a', 1), ('b', 3)) 
// equal = true 

이 빈약 예를 들어 내가 부동 소수점 값이 안정적으로 오히려 등호, 다음과 비교되지 않는 점을 감안 여기 등 특정 테스트 라이브러리의 코드, 를 생략하고있어 해당하는 예는 실패 : 내가 원하는 것이 무엇

def compute() = 
{ 
    // do a lot of computations here to produce the set below... 
    Set(('a', 1.0/3.0), ('b', 3.1)) 
} 

val A = compute() 
val equal2 = A == Set(('a', 0.33333), ('b', 3.1)) // Use some arbitrary precision here 
// equal2 = false 

정밀도의 임의의 수준을 사용하는 전화의 모든 부동 소수점 비교를 할 수있는 방법을하는 것입니다. 그러나 Set 나 Double 중 하나를 제어하지는 않습니다.

이중에서 새 클래스로 암시 적 변환을 정의한 다음 해당 클래스를 오버로드하여 true를 반환하려고했습니다. 그런 다음 하드 코드 된 유효성 검사에서 해당 클래스의 인스턴스를 사용할 수 있습니다.

implicit class DoubleAprox(d: Double) 
{ 
    override def hashCode = d.hashCode() 
    override def equals(other : Any) : Boolean = other match { 
     case that : Double => (d - that).abs < 1e-5 
     case _ => false 
    } 
} 

val equals3 = DoubleAprox(1.0/3.0) == 0.33333 // true 
val equals4 = 1.33333 == DoubleAprox(1.0/3.0) // false 

그러나 알 수 있듯이 대칭이 깨집니다. 따라서 더 복잡한 데이터 구조 (집합, 튜플, 사례 클래스)를 비교할 때 왼쪽 또는 오른쪽으로 equals()을 호출하면 선험적으로 정의 할 방법이 없습니다. 모든 구조체를 가로 지르고 그 지점에서 단일 부동 소수점 비교를 수행하는 것처럼 보입니다 ... 그래서 질문은 다음과 같습니다.이 작업을 수행 할 수있는 방법이 있습니까? 보조 노트로


는 : 나는 entire chapter on object equality 여러 블로그에 좋은 읽기를했다,하지만 그들은 단지 상속 문제에 대한 솔루션을 제공하며 기본적으로 관련된 모든 클래스를 소유하고 그들 모두를 변경해야합니다. 그리고 그것 모두는 해결하려고하는 것을 감안할 때 다소 복잡한 것처럼 보입니다.

평등은 메소드가 각 클래스에 추가되고 영구적으로 재정의되어야하기 때문에 Java에서 근본적으로 손상되는 것 중 하나입니다. 필자가 직관적으로 생각하는 것은 컴파일러가 찾을 수있는 비교 방법을 갖는 것입니다. 예를 들어, equals (DoubleAprox, Double)를 제공하면 클래스의 두 객체를 비교할 때마다 사용됩니다.

답변

2

평등의 의미를 퍼지로 바꾸는 것이 나쁜 생각이라고 생각합니다. 이유에 대해서는 Equals for case class with floating point fields에 내 의견을 참조하십시오.

그러나 매우 제한된 범위에서이를 수행하는 것이 좋습니다. 시험용. 나는 수치 문제에 대해 당신이 의존성으로 spire library을 사용하는 것을 고려해야한다고 생각한다. 그것은 많은 유용한 것들을 포함합니다. 그 중 평등을위한 유형 클래스와 개별 스칼라 유형에 대한 유형 클래스 인스턴스를 기반으로 복합 유형 (컬렉션, 튜플 등)에 대한 유형 클래스 인스턴스를 파생시키는 메커니즘.

자바 세계에서 평등이 깨 졌으므로 다른 연산자 (형식 안전성 동등성을 위해 ===)를 사용하고 있습니다.

// import the machinery for operators like === (when an Eq type class instance is in scope) 
import spire.syntax.all._ 

object Test extends App { 
    // redefine the equality for double, just in this scope, to mean fuzzy equali 
    implicit object FuzzyDoubleEq extends spire.algebra.Eq[Double] { 
    def eqv(a:Double, b:Double) = (a-b).abs < 1e-5 
    } 

    // this passes. === looks up the Eq instance for Double in the implicit scope. And 
    // since we have not imported the default instance but defined our own, this will 
    // find the Eq instance defined above and use its eqv method 
    require(0.0 === 0.000001) 

    // import automatic generation of type class instances for tuples based on type class instances of the scalars 
    // if there is an Eq available for each scalar type of the tuple, this will also make an Eq instance available for the tuple 
    import spire.std.tuples._ 
    require((0.0, 0.0) === (0.000001, 0.0)) // works also for tuples containing doubles 

    // import automatic generation of type class instances for arrays based on type class instances of the scalars 
    // if there is an Eq instance for the element type of the array, there will also be one for the entire array 
    import spire.std.array._ 
    require(Array(0.0,1.0) === Array(0.000001, 1.0)) // and for arrays of doubles 

    import spire.std.seq._ 
    require(Seq(1.0, 0.0) === Seq(1.000000001, 0.0)) 
} 
+0

코드 샘플을 보내 주셔서 감사합니다! 따라서 기본적으로 집합 평등을 위해 ==의 내부 사용을 조정할 방법이 없지만 평등을 재정의하는 전체 라이브러리를 가져올 수 있습니까? 튜플과 배열 같은 일반적인 컬렉션 (이미 Seq와 Map)에 대한 구현이 이미 있다면 의미가 있습니다. 그 도서관을 조사 할거야. 네가 의미하는 바를 이해하고 있니? –

+0

정확하지 않습니다. 아이디어는 스칼라 언어와 핵심 라이브러리 구문 (Tuples, Arrays, Seqs)을 계속 사용하고 spire와 같은 유형 클래스 기반 라이브러리의 기계를 사용하여 사용자 정의 동등 연산자 "==="를 제공하여 사용자가 무엇을 할 수 있는지 네가 원해. 위의 예에서 이들은 표준 스칼라 튜플 및 (자바) 배열이며 일부 사용자 정의 유형이 아닙니다. 그들은 그 자체를 정의하지 않습니다. 사실 뒤에 ===가 추가됩니다. 이 접근법은 scala Seq에도 적용됩니다. 그러나, *** 당신이 평등의 퍼지 개념으로 세트를 사용하는 것에 대해 잊어야한다고 생각합니다. 그건 기본적으로 불가능합니다. *** –

+0

네, 이해합니다. 불가능에 관해서는, 나는 그것이 너무 퍼지기를 원하지 않는다. (단순한 반올림 에러에 실패하지 않기를 바란다.) 대부분의 테스트에서 그것은 아마도 괜찮을 것이고, 불일치를 야기하지 않을 것이지만, 나는 당신의 요지를 얻는다. 감사! –

2

Java equals은 Scalaz 'Equal===과 같은 용도로 매우 귀찮은 사람들입니다. 그러나 그것은 심지어 관련 유형의 대칭을 가정합니다. 이기종 유형을 비교할 수 있도록 사용자 정의 typeclass를 작성해야한다고 생각합니다.

Shapeless 'automatic type class instance derivation을 사용하여 새 typeclass를 작성하고 사례 클래스에 대해 재귀 적으로 파생 된 인스턴스를 만드는 것은 매우 쉽습니다. 비록 그것이 두 개의 매개 변수 typeclass로 확장 모르겠어요. 별개의 EqualityLHSEqualityRHS 타이프 안경을 작성한 다음 A: EqualityLHSB: EqualityRHS을 비교하는 사용자 자신의 동일성 방법을 찾을 수 있습니다. 원하는 경우 연산자로 A을 입력 할 수 있습니다.(물론이 대안을 필요로하지 않고 전체 매개 변수를 지원하기 위해 기술을 일반적으로 확장 할 수 있어야하며, 나는 엉성한 것이 그러한 기여에 크게 기여할 것이라고 확신합니다).

행운을 빕니다 - 잘하면이 답변으로 나머지 답변을 충분히 찾을 수 있습니다. 당신이하고 싶은 일은 결코 사소한 일이 아니지만 현대 스칼라 기법의 도움으로 가능성의 범위 안에서 매우 중요합니다.

+0

야 :

다음은 테스트 결과를 비교하기위한 퍼지 평등을 얻기 위해 제한된 범위에 대한 평등을 재정의 할 방법 예입니다! 대답 주셔서 감사합니다, 투표를 해 줬습니다. 문제는 만약 내 자신의 비교 메서드 (=== 또는 일부)를 정의하면 Set.equals는 그것을 호출하지 않는다는 것입니다. 암시 적 변환을 정의하면 스칼라에서는 필요하지 않습니다. 한면의 유형을 변경하면 다른면의 기본 유형을 사용하여 equals()를 호출 할 수 있습니다. 자동 유형 클래스 인스턴스 파생을 살펴보고 전에는 들어 본 적이 없습니다! –

+0

그래, 너는'Set'의 좋아하는 것을 통해 모든 것을 파급시켜야하고,'Set'을 비교하기 위해 새로운 종류의 평등을 사용해야합니다. 여러분이 말했듯이,'Set.equals'는 이미 정의되었고, 이미'.equals'을 프리미티브 타입으로 호출하고 있습니다. 따라서 재정의 할 방법이 없습니다 (호출의 전체 체인을 다시 작성하는 매크로와 같은 것). – lmm