Как получить общую (полиморфную) лямбда в scala?

Обновление (2018 г.): на мои молитвы был дан ответ в Dotty (Type Lambdas), поэтому следующий Q & A является более "Scalac" о связанных


Простой пример из Scala:

scala> def f(x: Int) = x
f: (x: Int)Int

scala> (f _)(5)
res0: Int = 5

Позвольте сделать его общим:

scala> def f[T](x: T) = x
f: [T](x: T)T

scala> (f _)(5)
<console>:9: error: type mismatch;
 found   : Int(5)
 required: Nothing
              (f _)(5)
                    ^

Посмотрите на eta-расширение полиморфного метода в Scala:

scala> f _ 
res2: Nothing => Nothing = <function1>

Сравнение с Haskell:

Prelude> let f x = x

Prelude> f 5
5
Prelude> f "a"
"a"
Prelude> :t f
f :: t -> t

Haskell сделал правильный тип [T] => [T] здесь.

Более реалистичный пример?

scala> identity _
res2: Nothing => Nothing = <function1>

Еще более реалистично:

scala> def f[T](l: List[T]) = l.head
f: [T](l: List[T])T

scala> f _
res3: List[Nothing] => Nothing = <function1>

Вы не можете сделать псевдоним для идентификации - вам нужно написать свою собственную функцию. Такие вещи, как [T,U](t: T, u: U) => t -> u (make tuple), невозможно использовать в качестве значений. Более общий - если вы хотите передать некоторую лямбду, которая полагается на общий тип (например, использует общую функцию, например: создает списки, кортежи, каким-то образом модифицирует их) - вы не можете этого сделать.

Итак, как решить эту проблему? Любое обходное решение, решение или аргументация?

P.S. Я использовал термин полиморфный лямбда (вместо функции), поскольку функция называется lambda

Ответ 1

В JVM/ Scala, а не значениях могут быть только общие методы. Вы можете сделать анонимный экземпляр, который реализует некоторый интерфейс (и дублирует его для каждого типа, с которым вы хотите работать):

trait ~>[A[_], B[_]] { //exists in scalaz
  def apply[T](a: A[T]): B[T]
}

val f = new (List ~> Id) {
  def apply[T](a: List[T]) = a.head
}

Или используйте бесформенный 'Poly, который поддерживает более сложные типы. Но да, это ограничение, и это требует работы.

Ответ 2

Мне очень нравится решение @Travis Brown:

import shapeless._

scala> Poly(identity _)
res2: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = [email protected]

-

scala> def f[T](x: T) = x
f: [T](x: T)T

scala> Poly(f _)
res3: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = [email protected]

-

scala> def f[T](l: List[T]) = l.head
f: [T](l: List[T])T

scala> val ff = Poly(f _)
ff: shapeless.PolyDefns.~>[List,shapeless.Id] = [email protected]

scala> ff(List(1,2,3))
res5: shapeless.Id[Int] = 1

scala> ff(List("1","2","3"))
res6: shapeless.Id[String] = 1
Конструктор

Poly (в некоторых случаях) даст вам eta-расширение в функцию Shapeless2 Poly1, которая (более-менее) действительно является общей. Однако он не работает для мультипараметров (даже с несколькими типами параметров), поэтому нужно "реализовать" Poly2 с подходом implicit + at (как предлагалось @som-snytt), например:

object myF extends Poly2 {
  implicit def caseA[T, U] = at[T, U]{ (a, b) => a -> b}
}

scala> myF(1,2)
res15: (Int, Int) = (1,2)

scala> myF("a",2)
res16: (String, Int) = (a,2)

P.S. Я бы очень хотел, чтобы это было частью языка.

Ответ 3

P∀scal является плагином компилятора, который обеспечивает более сжатый синтаксис для кодирования полиморфных значений как объектов с помощью общего метода.

Функция идентификации, как значение, имеет тип ∀A. A => A. Чтобы перевести это в Scala, возьмите черту

trait ForAll[F[_]] {
  def apply[A]: F[A]
}

Тогда тождественная функция имеет тип ForAll[λ[A => A => A]], где я использую синтаксис kind-projector, или без любезного проектора:

type IdFun[A] = A => A
type PolyId = ForAll[IdFun]

И вот теперь синтаксический сахар P∀scal:

val id = Λ[Α](a => a) : PolyId

или эквивалентно

val id = ν[PolyId](a => a)

( "ν" - греческая строчная буква "Nu", читается "новая" )

Это действительно короткие сокращения для

new PolyId {
  def apply[A] = a => a
}

Множественные параметры и параметры произвольного типа поддерживаются P∀scal, но для каждого варианта вам нужно специальное изменение для вышеприведенного признака ForAll.

Ответ 4

Кажется, вам нужно будет немного намекать на тип, чтобы помочь системе вывода типа Scala.

def id[T] : T => T = identity _

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