2016-10-20 8 views
8

Дано:Очистка `случая class` с` Option` ПОЛОГО

case class Foo(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int]) 

Я хотел бы, чтобы только построение Fooтолько, если хотя бы один из ее аргументов Some, то есть не все поля None.

Было бы совсем немного коды, чтобы написать алгебраический тип данных, а затем сделать подклассы для каждого варианта:

sealed trait Foo 
case class HasAOnly(a: Int)  extends Foo 
case class HasAB(a: Int, b: Int) extends Foo 
// etc... 

Есть уборщик, то есть меньше коды, способ решения моей проблемы используя shapeless?

ответ

5

Благодаря sealed abstract case class трюк, который Роб Норрис недавно publicised, вы можете сохранить характеристики вашего Foo случае класса, но и предоставить свои собственные смарт-конструктор, который возвращает Option[Foo] в зависимости от того, проходят ли данные аргументы все ваши критерии или нет :

sealed abstract case class Foo(
    a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int]) 

object Foo { 
    private class Impl(
    a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int]) 
    extends Foo(a, b, c, d) 

    def apply(
    a: Option[Int], 
    b: Option[Int], 
    c: Option[Int], 
    d: Option[Int]): Option[Foo] = 
    (a, b, c, d) match { 
     case (None, None, None, None) => None 
     case _ => Some(new Impl(a, b, c, d)) 
    } 
} 
6

Вы можете сделать что-то подобное с вложенными Ior с:

import cats.data.Ior 

case class Foo(iors: Ior[Ior[Int, Int], Ior[Int, Int]]) { 
    def a: Option[Int] = iors.left.flatMap(_.left) 
    def b: Option[Int] = iors.left.flatMap(_.right) 
    def c: Option[Int] = iors.right.flatMap(_.left) 
    def d: Option[Int] = iors.right.flatMap(_.right) 
} 

Теперь это невозможно построить Foo со всеми None с. Вы также могли бы сделать конструктор класса case закрытым и иметь логику Ior в альтернативном конструкторе на сопутствующем объекте, что сделало бы образец немного более приятным, но это также сделает пример немного дольше.

К сожалению, это неловко использовать. То, что вы действительно хотите, является обобщением Ior таким же образом, что shapeless.Coproduct является обобщением Either. Я лично не знаю о готовой версии чего-либо подобного.

0

Я бы рекомендовал предоставить шаблон строителя для вашего класса. Это особенно полезно, если пользователи вашей библиотеки обычно указывают только некоторые из многих необязательных параметров. И в качестве бонуса с отдельными методами для каждого параметра им не нужно обертывать все в Some

Вы можете использовать параметр одного типа в классе, чтобы отметить, если он завершен (т.е. имеет хотя бы один параметр Some), и вы может принудительно применять это в методе построения с неявным параметром.

sealed trait Marker 
trait Ok extends Marker 
trait Nope extends Markee 

case class Foo private(a: Option[Int], b: Option[Int], c: Option[Int], d: Option[Int]) 

object Foo{ 
    case class Builder[T <: Marker](foo: Foo){ 
    def a(x:Int) = Builder[Ok](foo = foo.copy(a=Some(x))) 
    def b(x:Int) = Builder[Ok](foo = foo.copy(b=Some(x))) 
    // ... 

    def build(implicit ev: T <:< Ok) = foo 
    } 

    def create = Builder[Nope](Foo(None, None, None, None)) 
} 

Я раньше экспериментировал с типовым сейфом. Этот метод имеет более сложный пример, хотя он также отслеживает, какое поле было настроено, чтобы его можно было извлечь позже, без небезопасного вызова Option.get. https://gist.github.com/gjuhasz86/70cb1ca2cc057dac5ba7