Domanda Come ottenere lambda generico (polimorfico) in scala?


Aggiornamento (2018): le mie preghiere hanno ricevuto risposta in Dotty (Tipo Lambdas), quindi il seguente Q & A è più correlato a "Scalac"


Un semplice esempio di Scala:

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

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

Facciamo generico:

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)
                    ^

Diamo un'occhiata all'Eta-Espansione del metodo polimorfico in Scala:

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

Confronto con Haskell:

Prelude> let f x = x

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

Haskell ha inferito il tipo corretto [T] => [T] Qui.

Esempio più realistico?

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

Ancora più realistico:

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

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

Non puoi fare l'alias per identità - devi scrivere la tua funzione. Cose come [T,U](t: T, u: U) => t -> u (rendere tuple) sono impossibili da usare come valori. Più generale - se vuoi passare qualche lambda che si basa su un tipo generico (ad esempio usa una funzione generica, ad esempio: crea liste, tuple, modificale in qualche modo) - non puoi farlo.

Quindi, come risolvere questo problema? Qualche soluzione, soluzione o ragionamento?

Post scriptum Ho usato termine lambda polimorfico (invece di funzione) in quanto la funzione è appena chiamata lambda


11
2017-07-21 04:42


origine


risposte:


Solo i metodi possono essere generici su JVM / Scala, non su valori. È possibile creare un'istanza anonima che implementa un'interfaccia (e duplicarla per ogni tipo di unità con cui si desidera lavorare):

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
}

O usa senza forma " Poly, che supporta tipi di caratteri più complicati. Ma sì, è un limite e richiede di lavorare in giro.


7
2017-07-21 09:06



Mi piace molto la soluzione di @Travis Brown:

import shapeless._

scala> Poly(identity _)
res2: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$1$2$@797aa352

-

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

scala> Poly(f _)
res3: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$2$2$@664ea816

-

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] = fresh$macro$3$2$@51254c50

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

scala> ff(List("1","2","3"))
res6: shapeless.Id[String] = 1

Poly costruttore (in alcuni casi) ti darà eta-espansione in Shapeless2 Poly1 funzione, che è (più-meno) veramente generica. Tuttavia non funziona con i multi-parametri (anche con parametri di tipo multi), quindi è necessario "implementare" Poly2 con implicit + at approccio (come suggerito da @ som-snytt), qualcosa come:

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)

Post scriptum Vorrei davvero vederlo come una parte della lingua.


1
2017-07-21 09:59



P∀scal è un plugin per compilatore che fornisce una sintassi più concisa per la codifica di valori polimorfici come oggetti con un metodo generico.

La funzione di identità, come valore, ha tipo ∀A. A => A. Per tradurlo in Scala, assumere un tratto

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

Quindi la funzione di identità ha tipo ForAll[λ[A => A => A]], dove uso il tipo-proiettore sintassi, o, senza proiettore tipo:

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

E ora arriva lo zucchero sintattico P∀scal:

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

o equivalentemente

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

("ν" è la lettera minuscola greca "Nu", leggi "nuova")

Queste sono davvero solo delle abbreviazioni per

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

Parametri di tipi multipli e parametri di tipi arbitrari sono supportati da P∀scal, ma è necessaria una variazione dedicata su quanto sopra ForAll tratto per ogni variante.


1
2017-08-10 15:23



Sembra che sia necessario fare un suggerimento di tipo bit per aiutare il sistema di inferenza del tipo Scala.

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

Quindi suppongo che se provi a passare l'identità come parametro a una chiamata di funzione e i tipi di quel parametro sono generici, non dovrebbero esserci problemi.


0
2017-07-21 05:14