2017-12-04 14 views
4

나는 세 개의 필드로 구성된 데이터를 가지고 있는데, String, IntDouble이라고 가정 해 보겠습니다.스칼라에서 3 가지 포함 또는 유형의 인코딩

이들은 모두 선택 사항이지만 모든 필드에 적어도 하나 이상의 필드가 포함되어야합니다.

type MyType = String Ior Int Ior Double과 같은 cats.data.Ior을 사용해 보았습니다. 그것은 작동하지만, 실제로 그것으로 물건을 할 때 조금 서투른 느낌. case class을 3 개의 Option과 함께 생성하고 모두 None 인 경우 생성자에서 예외를 던지기도해야합니다.하지만 그 생각은 마음에 들지 않습니다.

다른 방법이 있는지 궁금합니다. (바람직하게는 Ior과 같은 유형으로 만 전체 정보를 인코딩하는 것이 좋지만 형식의 값을 사용하면 복잡하지는 않습니다.) . 다른 도서관 이용에 대한 제안도 환영합니다.

+2

3 개의 옵션 값으로 클래스를 사용하는 것이 좋습니다. 생성자를 private로 선언하고 정의 할 수 있다고 생각합니다. 조건을 검증하는 인스턴스를 생성하는 메소드는'Option [YourClass]','[Error, YourClass]'또는 코드베이스에서 사용중인 것을 반환 할 수 있습니다. – AlexITC

+0

당신이 옳다고 생각합니다. 나는 타입을 사용하여 이것을 인코딩하는 더 자연스러운 방법이 있는지 궁금하다. – yhm

+0

'cats.Ior' 또는 7 클래스'trait'을 사용하는 것이 이론적으로 건전한 접근 방법이라고 생각합니다. 'cats.Ior'의 소멸자는 3 방향'fold '이며, 그 결과 3 방향 유형에 대해 7 방향'fold'가됩니다. 이것은 어색함을 느낀다. 이 가치들을 가지고 무엇을 할 계획입니까? 세 가지 필드 사이에 어떤 자연스러운 우선 순위가 있습니까? 이 질문에 답하면 "올바른"인코딩을 결정하기가 더 쉽습니다. – ziggystar

답변

1

당신은 아마뿐만 아니라이 아이디어의 생각,하지만 이런 일에 대해 :

class StringOrIntOrDouble private(val first: Option[String], val second: Option[Int], val third: Option[Double]) { 
    def this(first: String) = this(Some(first), None, None) 

    def this(second: Int) = this(None, Some(second), None) 

    def this(third: Double) = this(None, None, Some(third)) 

    def this(first: String, second: Int) = this(Some(first), Some(second), None) 

    def this(second: Int, third: Double) = this(None, Some(second), Some(third)) 

    def this(first: String, third: Double) = this(Some(first), None, Some(third)) 

    def this(first: String, second: Int, third: Double) = this(Some(first), Some(second), Some(third)) 

} 

주요 생성자는 그래서 당신은 빈 인스턴스를 만들 수 없습니다 private입니다. 물론 Productequals, hashCode, toString 및 기타 유용한 것들을 케이스 클래스처럼 확장 할 수 있습니다 (단, 불변 값을 쉽게 깨뜨릴 수도 있음을 유의하십시오.) copy.

불행히도, 일반 버전을 원한다면 (또는 두 유형에서 동일한 유형을 사용하는 경우) 이름이 지정된 메소드에 대한 모든 "부분 집합"생성자를 컴패니언 오브젝트로 이동해야합니다. 그렇지 않으면 유형 삭제로 인해 컴파일되지 않습니다 .

class TripleIor[+A, +B, +C] private(val first: Option[A], val second: Option[B], val third: Option[C]) { 

} 

object TripleIor { 
    def first[A](first: A) = new TripleIor[A, Nothing, Nothing](Some(first), None, None) 

    def second[B](second: B) = new TripleIor[Nothing, B, Nothing](None, Some(second), None) 

    def third[C](third: C) = new TripleIor[Nothing, Nothing, C](None, None, Some(third)) 

    def firstAndSecond[A, B](first: A, second: B) = new TripleIor[A, B, Nothing](Some(first), Some(second), None) 

    def secondAndThird[B, C](second: B, third: C) = new TripleIor[Nothing, B, C](None, Some(second), Some(third)) 

    def firstAndThird[A, C](first: A, third: C) = new TripleIor[A, Nothing, C](Some(first), None, Some(third)) 

    def apply[A, B, C](first: A, second: B, third: C) = new TripleIor[A, B, C](Some(first), Some(second), Some(third)) 
} 

더 급진적 인 방법은 sealed trait 7 서브 클래스와 같은 아이디어를 구현하는 것입니다하지만 난 그들이 사용하는 것이 훨씬 쉬울 것이라고 생각하지 않습니다. 또한 copy을 유형에 안전한 방식으로 구현할 수 있지만 많은 타이핑 비용이 필요합니다 (여기에 표시되지 않음).

sealed trait TripleIor[A, B, C] extends Product { 
    def firstOption: Option[A] 

    def secondOption: Option[B] 

    def thirdOption: Option[C] 
} 

object TripleIor { 

    final case class First[A](first: A) extends TripleIor[A, Nothing, Nothing] { 
    override def firstOption: Option[A] = Some(first) 

    override def secondOption: Option[Nothing] = None 

    override def thirdOption: Option[Nothing] = None 
    } 

    final case class Second[B](second: B) extends TripleIor[Nothing, B, Nothing] { 
    override def firstOption: Option[Nothing] = None 

    override def secondOption: Option[B] = Some(second) 

    override def thirdOption: Option[Nothing] = None 
    } 

    final case class Third[C](third: C) extends TripleIor[Nothing, Nothing, C] { 
    override def firstOption: Option[Nothing] = None 

    override def secondOption: Option[Nothing] = None 

    override def thirdOption: Option[C] = Some(third) 
    } 

    final case class FirstSecond[A, B](first: A, second: B) extends TripleIor[A, B, Nothing] { 
    override def firstOption: Option[A] = Some(first) 

    override def secondOption: Option[B] = Some(second) 

    override def thirdOption: Option[Nothing] = None 
    } 

    final case class SecondThird[B, C](second: B, third: C) extends TripleIor[Nothing, B, C] { 
    override def firstOption: Option[Nothing] = None 

    override def secondOption: Option[B] = Some(second) 

    override def thirdOption: Option[C] = Some(third) 
    } 

    final case class FirstThird[A, C](first: A, third: C) extends TripleIor[A, Nothing, C] { 
    override def firstOption: Option[A] = Some(first) 

    override def secondOption: Option[Nothing] = None 

    override def thirdOption: Option[C] = Some(third) 
    } 

    final case class All[A, B, C](first: A, second: B, third: C) extends TripleIor[A, B, C] { 
    override def firstOption: Option[A] = Some(first) 

    override def secondOption: Option[B] = Some(second) 

    override def thirdOption: Option[C] = Some(third) 
    } 
} 

P. 여기에있는 모든 예제는 많은 유용하거나 필수적인 것을 구현하지 않는 아이디어를 설명하기위한 스케치 일뿐입니다.

+0

@yhm, 당신 말이 맞아요. 특정 유형에 대해 특정 클래스를 원한다면 그 접근 방식이 효과적입니다.만약 그것이 일반화되어야한다면, 형식 소거 때문에 "하위 집합"생성자는 다르게 이름을 붙여야하고 그래서 동반자 객체로 옮겨야합니다. 이렇게하면 코드가 더 추해지며 밀폐 된 특성 접근 방식이 더욱 매력적으로됩니다. – SergGr

+0

이 경우 지금 당장 가지고 있습니다. 특정 클래스 만 필요하므로이 접근법이 좋을 것 같습니다. – yhm

+1

@yhm, OK 그렇기 때문에 유형별 케이스를 되돌려 놓았으며 실제로 완전한 제네릭 솔루션을 구현하는 것이 더 쉬울 수도있는 밀폐 된 특성에 대한 스케치를 추가했습니다. – SergGr