2011-09-08 4 views
9

잘 정의 된 관계를 지닌 속성을 가진 특성을 정의하고 싶습니다. 예를 들어, a * b = c이라고 가정 해 봅시다. 아이디어는이 특성의 구현이 두 가지를 제공하고 자동으로 파생 된 세 번째 속성에 대한 접근자를 가질 수 있다는 것입니다. 당신이 나머지로 너무 오래 기능의 부분 집합을 정의 할 수 있지만 -스칼라에서 상호 의존적 인 기본 메소드 구현을 추론하기

(이 하스켈의 타입 클래스와 같은 기능은 내가 정확히, 당신은 Ord에서 <을 정의 된 경우 어디 사람들을 기억한다면, >=! . <로 구현 될 수있다 . 추론 할 수있다) (I 제대로 하스켈의 타입 클래스를 기억하지 않는다)

초보적인 방법은 실제로 꽤 잘 작동합니다.

여기
trait Foo { 
    // a * b = c 
    def a: Double = c/b 
    def b: Double = c/a 
    def c: Double = a * b 
} 

class FooOne(override val a: Double, override val b: Double) extends Foo 
class FooTwo(override val a: Double, override val c: Double) extends Foo 

, 구현를및 FooTwoFoo의 완전한 구현이며 예상대로 작동합니다. 여태까지는 그런대로 잘됐다; 이 접근 방식은 클래스가 두 개의 속성을 정의하고 세 번째 속성을 "무료"로 가져올 수있게합니다.

그러나, 물건 하나 제 3 클래스를 정의하는 경우 덜 장미 빛보고 시작합니다

class FooFail(override val a: Double) extends Foo 

이 잘 컴파일 - 그 b 또는 c 방법이 지금까지 평가하는 경우 그러나, 그것은 스택 오버 플로우가 발생합니다.


그래서 순진한 접근 방법은 하스켈의 타입 클래스 방식의 추론 측면을 제공하지만, 우리는 컴파일시 안전성이 없습니다. 클래스의 구현에 의해 두 개 미만의 메소드가 정의되는 경우 컴파일러가 불평 할 것입니다. 분명히 현재 구문만으로는 충분하지 않습니다. 의존적 인 메소드가 추상적이지 않은 경우에만 사용될 수있는 기본 구현이긴하지만, 추상화 된 메소드를 필요로합니다.

Scala는 이것을 정의하기 위해 적절한 의미를 폭로합니까? (이 언어에 대한 어떤 일류 지원도 알지 못하기 때문에 union types과 비슷한 다소 둥근 방법을 정의하면 문제가 없습니다.)

그렇지 않다면, 나는 순진한 접근법을 사용하고 수업을 신중하게 정의하고 테스트 할 것입니다. 그러나 저는 이것이 타입 시스템이 잡을 수 있어야한다고 생각합니다 (결국 루비가 아닙니다. :)).

+2

완전히 잊어 버리지 않는 한 하스켈은 똑같은 문제에 대해 똑같은 순진한 접근법을 사용합니다. –

+0

@Alexey - 흥미로운 의견입니다. 그것은 오래되었지만 당신이 메서드를 충분히 정의하지 않으면 컴파일러/인터프리터 오류가 발생하여 형식 클래스의 모든 메서드를 파생시킬 수 없다는 것을 상기하는 것 같습니다. 어쩌면 이것을 재현 해 보도록하겠습니다. 하스켈이 영감을 얻으려는 방법에 대해 읽어 봅니다. –

+0

OK, 하스켈에서 이것을 테스트 한 결과 실제로 똑같은 문제가 발생했습니다 (완전히 추상적 인 메소드에 대해서만 경고한다는 점에서 스칼라 상황과 동일합니다). 즉, 다른 언어로 선례가 없더라도 여전히 의문이 남아 있습니다. –

답변

8

사용 방법 :

object test { 
    case class A(v : Double) 
    case class B(v : Double) 
    case class C(v : Double) 

    implicit def a(implicit b : B, c : C) = A(c.v/b.v) 
    implicit def b(implicit a : A, c : C) = B(c.v/a.v) 
    implicit def c(implicit a : A, b : B) = C(a.v * b.v) 

    def foo(implicit a : A, b : B, c : C) = 
    a + ", " + b + ", " + c 

    // Remove either of these and it won't compile 
    implicit val aa = A(3) 
    implicit val bb = B(4) 

    def main(args : Array[String]) { 
    println(foo) 
    } 
} 
+0

매우 흥미로운 접근 방식입니다. 제 의견에 따르면 충분한 정의가 있는지 컴파일러에서 확인하는 것이 중요합니다. 소비자가 'val b = 5'를 더 이상 선언 할 수 없다는 것은 수치스러운 일이다. 이것이 내면의 다른 특징을 통해 마사지 될 수 있는지 궁금합니다. –

5

하스켈에서 어떻게 안전합니까? 나는 언어에 익숙하지 않지만 할 수있다. 비교 평가를 할 때 스택 오버 플로우가 발생한다.

나는 스칼라에서 해결 방법을 상상할 수 있지만 약간 약하다. 먼저, 그 다음

trait FooWithAB extends Foo {def c : Double = a * b} 
trait FooWithAC extends Foo {def b : Double = c/a} 
trait FooWithBC extends Foo {def a : Double = c/b} 

FooOne 및 FooTwo의 특성 중 하나에 혼합 할 것이다 허용 각 방법 정의 조합을 믹스 인 특성을 만들

trait Foo { def a: Double; def b: Double; def c: Double } 

완전히 추상적 인 푸 둡니다.FooFail에서 아무 것도 당신이 두 사람을 섞지 못하게하고, 여전히 실패하지만, 당신은 다소 경고를 받았습니다. 당신이 정의 할 수 없습니다

되어

trait Foo { 
    type t; 
    def a: Double; def b: Double; def c: Double 
} 
trait FooWithAB extends Foo {type t = FooWithAB; def c : Double = a * b} 
trait FooWithAC extends Foo {type t = FooWithAC; def b : Double = c/a} 
trait FooWithBC extends Foo {type t = FooWithBC; def c : Double = c/b} 

이쪽 FooWithXX 두 가지를 혼합 방지 팬텀 유형의 종류, 한 단계 더 나아가 그 두 가지를 혼합 금지 할 수

class FooFail(val a: Double) extends FooWithAC with FooWithAB 
+0

좋은 지적 - 어떤 해결책이든 메서드를'Foo'에서 완전히 추상화 한 다음 * 어딘가에있는 * 기본 정의에서 혼합하는 것이 포함될 것이라고 생각합니다. 팬텀 유형의 접근 방식은 흥미 롭다. 비록 분명히 특성에 섞여 야하는 소비자에게 clunkiness가 노출된다는 것은 수치 스럽다. 아마도 암시 적 변환은 어떻게 든이 문제를 해결할 수 있습니다. 흠 ... –

2

약간 약한 용액 다음됩니다 할 수 있습니다 :

trait Foo { 
    def a : Double 
    def b : Double 
    def c : Double 
} 

// Anything with two out of three can be promoted to Foo status. 
implicit def ab2Foo(ab : { def a : Double; def b : Double }) = 
    new Foo { val a = ab.a; val b = ab.b; def c = ab.a * ab.b } 
implicit def bc2Foo(bc : { def b : Double; def c : Double }) = 
    new Foo { val a = bc.c/bc.b; val b = bc.b; def c = bc.c } 
implicit def ac2Foo(ac : { def a : Double; def c : Double }) = 
    new Foo { val a = ac.a; val b = ac.c/ac.a; def c = ac.c } 

여기, 세 가지 (A)의 두 어떤 클래스, B, C의 방법은 볼과 푸로 사용할 수 있습니다. 예를 들면 :

case class AB(a : Double, b : Double) 
AB(5.0, 7.1).c // prints 35.5 

하지만 예를 들어 시도하는 경우 :

case class ABC(a : Double, b : Double, c : Double) 
val x : Foo = ABC(1.0, 2.0, 3.0) 

을 ... 당신은 "모호한 암시"오류가 발생합니다.

원래 문제는 클래스가 Foo에서 적절하게 상속되지 않는다는 것입니다. 다른 방법을 다시 사용하려면 문제가 될 수 있습니다. 이 다른 메소드를 포함하는 다른 형 태인 FooImpl을 사용하여이를 해결할 수 있으며 암시 적 변환을 FooImpl의 부속 유형으로 제한합니다.

+0

나는 여기서 한 일을 좋아한다. 소비자에게 추가 요구 사항을 두지 않고 컴파일 타임에 검사를 수행하기 위해 암시 적 변환과 함께 구조 유형을 사용한다. 'Foo '가 이상적이지 않다는 것에 동의하지만, 다른 요구 사항을 모두 얻으려면 피할 수없는 두려움이 있습니다 (그렇지 않으면'Foo'의 메소드가 동시에 추상적이고 정의되어 있어야합니다). –

관련 문제