Типы объединения Scala с замыканиями

Я пробую объединить типы Scala, определенные в этой записи блога Майлза Сабина:

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

а также обсуждается в

Как определить дизъюнкцию типов (типы объединения)?

Для простого случая, определенного там, они работают нормально, но я пытаюсь использовать их для создания общего парсера JSON в Play Framework, который будет принимать только определенные значения (String, Boolean, Int, Undefined), а затем успешно передавать их .

Вот мой код:

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

Который я затем хотел бы использовать таким образом:

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

Но моя функция withValue выдает ошибку компиляции:

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

Разве T не должен быть UpdateType, который должен принимать Some [String], или я чего-то не понимаю?

Есть идеи, что я могу сделать здесь, чтобы они работали, или это возможно? Я новичок в более продвинутых типах и лямбдах типов в Scala, но я пытаюсь понять, как они работают, чтобы создать более безопасный код.

Я также заметил, что у Майлза Сабина есть библиотека под названием Shapeless (https://github.com/milessabin/shapeless), который я искал, но не смог найти там ничего, что могло бы сделать то же самое, что я пытаюсь достичь здесь.


person Klaus    schedule 30.04.2014    source источник
comment
Я не уверен, почему это не компилируется, но даже если это произошло, стирание типа сделает то, что вы делаете, опасным. У вас будут ссылки либо на Option[String], либо на Option[Int], и из-за стирания типа вы не сможете отличить их друг от друга. Это определенно не более типобезопасный код. Лучшим решением было бы объявить вашу собственную коллекцию классов, происходящих от общего подкласса, по одному для каждого из типов, которые вы объединяете, и по одному для замены None. На самом деле это более строгая типизация, потому что у вас есть свои собственные конкретные типы для этого варианта использования, а не повторное использование Option.   -  person wingedsubmariner    schedule 30.04.2014
comment
Вы имеете в виду класс UpdateType, а затем подклассы, такие как UpdateTypeString, UpdateTypeNone и т. Д.? Разве это не был бы просто менее общий способ сделать именно то, что я пытаюсь сделать здесь с объединениями типов? В обоих случаях мне нужно было бы в конце шаблон сопоставить типы.   -  person Klaus    schedule 30.04.2014
comment
Да, но с классом UpdateType и дочерними элементами сопоставление с образцом действительно работает. Кроме того, если UpdateType отмечен sealed, компилятор может проверить, не забыли ли вы какие-либо типы. OO действительно является правильным способом сделать это в Scala, программирование на уровне типов предназначено для людей, которым нужно выставлять напоказ, но оно не относится к производственному коду.   -  person wingedsubmariner    schedule 30.04.2014
comment
Я не знаю. Это работает, но, в конце концов, теперь есть шаблон, который распространяется по всему моему коду, где мне нужно только что-то, что просто гарантирует, что я могу передать методу только String, Int или Boolean.   -  person Klaus    schedule 30.04.2014
comment
будет ли Coproduct in shapeless полезным в этом случае github.com/milessabin/shapeless/wiki/?   -  person stanislav.chetvertkov    schedule 12.05.2016


Ответы (2)


К сожалению, типы объединения Сабина нельзя использовать только как возвращаемый тип. Связанный контекст [T : (UpdateType)#λ] вашего метода - это просто синтаксический сахар для дополнительного неявного параметра (implicit evidence: (UpdateType)#λ[T]). Этот параметр является экземпляром <:<, доказывающим, что T является подтипом одного из компонентов типа объединения. Компилятор должен заполнить параметр в месте вызова метода, и для этого ему необходимо знать, какой из компонентов типа объединения T будет. Это не проблема, если T является типом одного из параметров, потому что компилятор будет иметь этот тип под рукой. Однако, когда T используется в качестве возвращаемого типа, компилятор не имеет возможности узнать, каким будет T. Что еще хуже, внутри самого метода T и неявный параметр уже высечены, поэтому компилятор может принимать только T в качестве возвращаемого значения, а T - только один из компонентов типа объединения, а не истинное объединение. тип, который может представлять любой из них. Вот почему вы получаете ошибку компилятора, утверждающую, что Some[String] не может использоваться как T.

Нет никакого простого способа исправить это, потому что Scala действительно не имеет типов объединения. Как я уже упоминал в комментариях к вопросу, существует простое объектно-ориентированное решение, хотя и с большим количеством шаблонов.

person wingedsubmariner    schedule 30.04.2014

Позвольте представить вам свое решение:

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

Проблема с этим решением заключается в том, что расширения Int или String не будут приниматься здесь напрямую ... Значения, введенные здесь, ограничиваются как "Контравариантность" Int with String.

Есть способ обойти это, вы должны обойти вывод типа и указать базовый класс в параметре типа, например:

sizeOf[String](someExtendOfString)
person RoyB    schedule 06.03.2018