Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleaning up `case class` with `Option` FIelds

Given:

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

I'd like to only allow constructing a Foo only if at least one of its arguments is Some, i.e. not all fields are None.

It would be quite a bit of code to write an Algebraic Data Type, and then make sub-classes for each variant:

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

Is there a cleaner, i.e. less code, way to address my problem using shapeless?

like image 822
Kevin Meredith Avatar asked Oct 20 '16 18:10

Kevin Meredith


1 Answers

You can do something like this with nested Iors:

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)
}

Now it's impossible to construct a Foo with all Nones. You could also make the case class constructor private and have the Ior logic happen in an alternative constructor on the companion object, which would make pattern matching a little nicer, but it would also make the example a little longer.

Unfortunately this is kind of clunky to use. What you really want is a generalization of Ior in the same way that shapeless.Coproduct is a generalization of Either. I'm not personally aware of a ready-made version of anything like that, though.

like image 111
Travis Brown Avatar answered Oct 04 '22 01:10

Travis Brown