Как получить общую (полиморфную) лямбда в 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 _
Итак, я думаю, если вы попытаетесь передать идентификатор в качестве параметра вызова функции, а типы этого параметра являются общими, тогда проблем не должно быть.