В Scala можно ли каррировать параметры типа def?

Предположим, у меня есть определение, которое принимает несколько параметров типа:

def foo[A, B, C](b: B, c: C)(implicit ev: Writer[A])

Однако предполагаемое использование заключается в том, что параметры типа B и C должны быть выведены (на основе переданных аргументов). И вызывающей стороне нужно только явно указать A (например, чтобы компилятор выбрал соответствующий имплицит). К сожалению, Scala позволяет вызывающему коду указывать только все параметры типа или ни один из них. В некотором смысле я хочу, чтобы параметры типа были каррированы:

def foo[A][B, C]...

Есть ли какой-нибудь трюк, чтобы сделать это в Scala?

(Если мой конкретный пример не имеет полного смысла, я буду рад улучшить его своими предложениями.)


person tksfz    schedule 17.08.2016    source источник


Ответы (1)


Лучший способ, которым я смог это осуществить, — определить класс, который содержит информацию о каррированном типе, а затем использует метод apply для имитации вызова функции.

Я писал об этом здесь - http://caryrobbins.com/dev/scala-type-curry/

Для вашего конкретного примера вам нужно поставить implicit ev: Writes[A] в подпись для apply и нет в подпись для foo. Это связано с тем, что возникает неоднозначность между явной передачей неявного аргумента и неявным вызовом метода apply.

Вот пример реализации для вашего примера -

object Example {
  def foo[A]: _Foo[A] = _foo.asInstanceOf[_Foo[A]]

  final class _Foo[A] private[Example] {
    def apply[B, C](b: B, c: C)(implicit ev: Writes[A]): Unit = ???
  }

  private lazy val _foo = new _Foo[Nothing]
}

Затем вы можете указать свой параметр типа, который вы хотите каррировать, и будут выведены следующие аргументы, переданные методу apply.

Example.foo[Int]("bar", new Object)

Если вам в конечном итоге потребуется указать другие параметры типа, вы можете сделать это, явно вызвав apply; хотя, я никогда не видел необходимости делать это еще.

Example.foo[Int].apply[String, Object]("bar", new Object)

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

person pyrospade    schedule 17.08.2016
comment
Очень хорошо написано, и очень умно! - person Alec; 17.08.2016
comment
умная. Однако вопрос, почему бы не сделать напрямую def foo[A] = new _Foo[A]? - person mathieu; 13.02.2020
comment
Вы бы выделяли новый экземпляр _Foo каждый раз, когда вызывается foo. Нет причин делать это, так как все, что мы действительно хотим сделать, это зафиксировать информацию о типе. Есть и другие оптимизации, которые можно сделать, это просто. - person pyrospade; 13.02.2020