1

I'm trying out Scala's union types defined in this Miles Sabin's blog post:

http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/

and also discussed in

How to define "type disjunction" (union types)?

For the simple case defined there they work ok, but what I'm trying to do is use them to make a generic JSON parser in Play Framework that would only accept certain values (String, Boolean, Int, Undefined) and then pass them successfully.

Here is my code:

type UpdateType = Option[String] |∨| Option[Int] |∨| Option[Boolean] |∨| Option[List[Int]]

def withValue[T : (UpdateType)#λ](request: Request[JsValue])(block: (String, T) => Future[SimpleResult]) = {
  val field = request.body \ ("field")
  val value = request.body \ ("value")
  (field, value) match {
    case (x: JsString, y: JsString) => block(x.value.toString, Some(y.value.toString))
    case (x: JsString, y: JsNumber) => block(x.value.toString, Some(y.value.intValue))
    case (x: JsString, y: JsBoolean) => block(x.value.toString, Some(y.value.booleanValue))
    case (x: JsString, y: JsUndefined) => block(x.value.toString, None)
    case _ => Future.successful(BadRequest(s"Incorrect field, value pair for ${request.body}."))
  }
}

Which I would then like to use in this manner:

def update(code: String) = Action.async(parse.json) { 
  request =>
    withValue(request) { (field, value) =>
      // Code that does something with the value
      Future(Ok)  
    }
}

But my withValue function gives a compile error:

[error]  found   : Some[String]
[error]  required: T
[error]       case (x: JsString, y: JsString) => block(x.value.toString, Some(y.value.toString))

Shouldn't T be UpdateType which should accept Some[String] or is there something I'm not getting here?

Any idea what I could do here to get these working or is it possible? I'm quite new to the more advanced types and type lambdas in Scala but I'm trying learn how they work to create more type safe code.

I also noticed that Miles Sabin has a library called Shapeless (https://github.com/milessabin/shapeless) which I was looking into, but couldn't find anything there that would do the same thing as I'm trying to achieve here.

Community
  • 1
  • 1
Klaus
  • 71
  • 1
  • 7
  • 1
    I'm not sure why this isn't compiling, but even if it did type erasure will make what you are doing dangerous. You will have references to either `Option[String]` or `Option[Int]` and because of type erasure you won't be able to tell them apart. This most definitely isn't "more type-safe code". A better solution would be to declare your own collection of classes descending from a common subclass, one for each of the types you were unioning and one to replace `None`. This in fact has stronger typing, because you have your own specific types for this use case, rather than reusing `Option`. – wingedsubmariner Apr 30 '14 at 03:42
  • Do you mean having a a class of UpdateType and then having subclasses like UpdateTypeString, UpdateTypeNone and so forth? Wouldn't this be just a less generic way of doing exactly what I'm trying to do here with type unions? In both cases I would need to at the end pattern match the types. – Klaus Apr 30 '14 at 03:52
  • Yes, but with the UpdateType class and children the pattern match will actually work. Also, if UpdateType is marked `sealed` the compiler can check you haven't forgotten any types. OO really is the right way to do this in Scala, type-level programming is for people to show off with but doesn't belong in production code. – wingedsubmariner Apr 30 '14 at 04:11
  • I don't know. This works but in the end now there's boilerplate that spreads throughout my code where I only wanted something that simply makes sure I can only pass String, Int or Boolean to a method. – Klaus Apr 30 '14 at 04:30
  • would Coproduct in shapeless be helpful in this case https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#coproducts-and-discriminated-unions ? – stanislav.chetvertkov May 12 '16 at 10:33

2 Answers2

7

Unfortunately, Sabin's union types cannot be used solely as a return type. The context bound [T : (UpdateType)#λ] of your method is just syntactic sugar for an extra implicit parameter, (implicit evidence: (UpdateType)#λ[T]). This parameter is an instance of <:< proving that T is a subtype of one of the components of the union type. The compiler must fill in the parameter at the call site of the method, and to do that it needs to know which of the components of the union type T is going to be. This isn't a problem when T is the the type of one of the parameters, because the compiler will have that type on hand. When T is used as the return type however the compiler has no way of of knowing what T will turn out to be. Worse still, inside the method itself, T and the implicit parameter have already been set in stone, and so the compiler can only accept T as a return value, and T is only one of the components of the union type, not a true union type that could represent any one of them. This is why you get the compiler error insisting that Some[String] can't be used as a T.

There isn't any easy way to fix this, because Scala does not truly have union types. As I mentioned in the comments to the question, there is a simple OO solution, albeit with more boilerplate.

wingedsubmariner
  • 13,350
  • 1
  • 27
  • 52
0

Let me introduce you to my solution:

//Add this to your util library
trait Contra[-A]
type Union[A,B] = Contra[A] <:< Contra[B]

//And see a usage example below
@implicitNotFound("Only Int or String can be sized")
type Sizeable[T] = Union[T, Int with String]

def sizeOf[T: Sizeable](sizeable: T): Int = {
  sizeable match {
    case i: Int => i
    case s: String => s.length
  }
}

Problem with this solution is that extends of Int or String would not be accepted here directly... Values entered here are being constrained to being "Contravariant" to Int with String.

There is a way around this tho, you have to circumvent the type inference, and supply the base class in the type parameter, like this:

sizeOf[String](someExtendOfString)
RoyB
  • 3,104
  • 1
  • 16
  • 37