Questa è stata una buona scusa per me di guardare in Shapeless, qualcosa che ho sempre voluto fare ad un certo punto :)
$ git clone [email protected]:milessabin/shapeless.git
...
$ cd shapeless
(1)
Shapeless fornisce alcune astrazioni sull'arità e, in particolare, la rappresentazione come elenco eterogeneo (HList
). Una funzione di arbitrarietà può essere vista come FnHList
(una funzione che accetta un argomento come HList
).
$ sbt shapeless-core/console
scala> import shapeless._
import shapeless._
scala> def isFunction[A](fun: A)(implicit fnh: FnHLister[A]) {}
isFunction: [A](fun: A)(implicit fnh: shapeless.FnHLister[A])Unit
scala> isFunction(math.sqrt _)
scala> isFunction(math.random _)
(2)
Ora diamo richiedono che la funzione restituisce un Double
:
scala> def isFunReturningDouble[A](fun: A)(implicit fnh: FnHLister[A] { type Result = Double }) {}
isFunReturningDouble: [A](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double})Unit
scala> isFunReturningDouble(math.sqrt _)
scala> isFunReturningDouble(math.signum _)
<console>:12: error: could not find implicit value for parameter fnh: shapeless.FnHLister[Int => Int]{type Result = Double}
isFunReturningDouble(math.signum _)
^
(3)
La classe LUBConstraint
tipo può testimoniare il limite superiore dell'argomento lista:
scala> def isValidFun[A, B <: HList](fun: A)(implicit fnh: FnHLister[A] { type Result = Double; type Args = B }, lub: LUBConstraint[B, Double]) {}
isValidFun: [A, B <: shapeless.HList](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double; type Args = B}, implicit lub: shapeless.LUBConstraint[B,Double])Unit
scala> isValidFun(math.random _)
scala> isValidFun((i: Int) => i.toDouble)
<console>:12: error: could not find implicit value for parameter lub: shapeless.LUBConstraint[B,Double]
isValidFun((i: Int) => i.toDouble)
^
(4)
Ora abbiamo ancora bisogno di estrarre l'arity in qualche modo. A livello di tipo questo sarebbe Length
fornito per HList
. Per ottenere un valore di runtime, è necessaria un'altra classe di tipo ToInt
.
Ecco la funzione finale:
import shapeless._
def doubleFunArity[A, B <: HList, C <: Nat](fun: A)(implicit
fnh: FnHLister[A] { type Result = Double; type Args = B },
lub: LUBConstraint[B, Double],
len: Length[B] { type Out = C },
res: ToInt[C]
): Int = res()
prova:
scala> doubleFunArity(math.sqrt _)
res15: Int = 1
scala> doubleFunArity(math.random _)
res16: Int = 0
scala> val g: (Double, Double) => Double = math.max _
g: (Double, Double) => Double = <function2>
scala> doubleFunArity(g)
res17: Int = 2
Nota che purtroppo molti math
operazioni sono sovraccarichi, e senza una forte vincolo di tipo, Scala non vi darà la Double
versione automaticamente, ma utilizzerà la versione Int
per qualche motivo:
scala> math.max _
res18: (Int, Int) => Int = <function2>
Quindi ho bisogno dell'indirizzamento indiretto math.max _: ((Double, Double) => Double)
per farlo funzionare.
Non dicendo che questo è il modo migliore per farlo nel tuo caso concreto, ma penso che sia stata un'esplorazione divertente.
Si dice che 'g' accetta una n-tupla, ma il tuo esempio' Math.max' è una funzione n-ario, non una 'Funzione1' che accetta una tupla. Dovresti chiarirlo. –
Risolto, buona cattura – tba