2 votes

Génériques Scala : Numérique

J'ai le code Java suivant :

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class NumTest {

    public static void main(String[] args) {
        final List<Integer> list1 = Arrays.asList(1, 2);
        final List<Float> list2 = Arrays.asList(3.0f, 4.0f);
        final List<Double> list3 = Arrays.asList(5.0, 6.0);
        assertCloseEnough(list1, Arrays.asList(1.0, 2.0));
        assertCloseEnough(list2, Arrays.asList(3.0, 4.0));
        assertCloseEnough(list3, Arrays.asList(5.0, 6.0));
    }

    private static void assertCloseEnough(List<? extends Number> actuals, List<? extends Number> expecteds) {
        assert actuals.size() == expecteds.size();
        for(int i = 0; i < actuals.size(); i++) {
            System.err.println(actuals.get(i).doubleValue());
            assert Math.abs(actuals.get(i).doubleValue() - expecteds.get(i).doubleValue()) < 1E-10;
        }
    }
}

Cela fonctionne comme prévu, comme vous pouvez le vérifier avec javac NumTest.java && java NumTest .

Ma question est la suivante : comment puis-je écrire l'équivalent en Scala ?

L'approche la plus simple :

import Numeric.Implicits._

object TestNum extends App {

  assertCloseEnough(Seq(1,2), Seq(1.0, 2.0))
  assertCloseEnough(Seq(3.0f,4.0f), Seq(3.0, 4.0))
  assertCloseEnough(Seq(5.0,6.0), Seq(5.0, 6.0))

  def assertCloseEnough[N: Numeric](actuals: Seq[N], expecteds: Seq[N]): Unit = {
    assert(actuals.size == expecteds.size)
    val ad = actuals.map(implicitly[Numeric[N]].toDouble(_))
    val ed = expecteds.map(implicitly[Numeric[N]].toDouble(_))
    for (i <- expecteds.indices) {
      assert(Math.abs(ad(i) - ed(i)) < 1E-10)
    }
  }
}

Ça ne marche pas :

TestNum1.scala:5: error: could not find implicit value for evidence parameter of type Numeric[AnyVal]
  assertCloseEnough(Seq(1,2), Seq(1.0, 2.0))
                   ^

Une version légèrement plus avancée :

import Numeric.Implicits._

object TestNum extends App {

  assertCloseEnough(Seq[Int](1,2), Seq[Double](1.0, 2.0))
  assertCloseEnough(Seq[Float](3.0f,4.0f), Seq[Double](3.0, 4.0))
  assertCloseEnough(Seq[Double](5.0,6.0), Seq[Double](5.0, 6.0))

  def assertCloseEnough[N: Numeric](actuals: Seq[N], expecteds: Seq[N]): Unit = {
    assert(actuals.size == expecteds.size)
    val ad = actuals.map(implicitly[Numeric[N]].toDouble(_))
    val ed = expecteds.map(implicitly[Numeric[N]].toDouble(_))
    for (i <- expecteds.indices) {
      assert(Math.abs(ad(i) - ed(i)) < 1E-10)
    }
  }
}

Cela ne fonctionne pas non plus, avec la même erreur.

Nous examinons ici d'autres questions telles que Génériques et implicites numériques en Scala Je suis arrivé à la conclusion suivante :

import Numeric.Implicits._

object TestNum extends App {

  assertCloseEnough(Seq(1,2), Seq(1.0, 2.0))
  assertCloseEnough(Seq(3.0f,4.0f), Seq(3.0, 4.0))
  assertCloseEnough(Seq(5.0,6.0), Seq(5.0, 6.0))

  def assertCloseEnough[N: Numeric, T1 <% N, T2 <% N](actuals: Seq[T1], expecteds: Seq[T2]): Unit = {
    assert(actuals.size == expecteds.size)
    val ad = actuals.map(implicitly[Numeric[T1]].toDouble(_))
    val ed = expecteds.map(implicitly[Numeric[T2]].toDouble(_))
    for (i <- expecteds.indices) {
      assert(Math.abs(ad(i) - ed(i)) < 1E-10)
    }
  }
}

Ce qui ne marche pas non plus :

TestNum3.scala:5: error: ambiguous implicit values:
 both object BigIntIsIntegral in object Numeric of type scala.math.Numeric.BigIntIsIntegral.type
 and object IntIsIntegral in object Numeric of type scala.math.Numeric.IntIsIntegral.type
 match expected type Numeric[N]
  assertCloseEnough(Seq(1,2), Seq(1.0, 2.0))
                   ^

Qu'est-ce que je rate ici ? Comment faire pour que ça marche ?

3voto

Dima Points 7274

Vos séquences comportent des éléments de deux types différents, mais vous essayez de les paramétrer comme une seule. Quelque chose comme ceci devrait fonctionner :

 def assertCloseEnough[N1, N2](expected: Seq[N1], actual: Seq[N2])(implicit e1: Numeric[N1], e2: Numeric[N2]) {
    assert(
      expected.size == actual.size &&
      (expected zip actual).forall { case (a,b) => 
         math.abs(e1.toDouble(a)-e2.toDouble(b)) < 1e-10
      }
   )
 }

Cette déclaration est équivalente à closeEnough[N1 : Numeric, N2 : Numeric]( ...) mais c'est un peu plus pratique dans ce cas, car cela donne des noms réels aux implicites de "preuve", de sorte que vous n'avez pas besoin de les repêcher à l'aide de implicitly[Numeric[N1]] ...

De même, n'utilisez pas foo(i) avec Seq c'est presque toujours une mauvaise idée. Si vous êtes sûr d'avoir besoin d'un accès aléatoire (la plupart du temps, ce n'est pas le cas), utilisez IndexedSeq à la place.

1voto

Tom Points 464

Vous devez utiliser deux types (par ex. T1 y T2 ), et fournissez des arguments implicites à votre méthode ou appelez un Numeric à partir de la portée implicite avec implicitly

Vous trouverez ci-dessous deux façons de procéder :

def assertCloseEnough[T1: Numeric, T2: Numeric](actuals: Seq[T1], expecteds: Seq[T2]): Unit = {
  assert(actuals.size == expecteds.size)
  val ad = actuals.map(implicitly[Numeric[T1]].toDouble)
  val ed = expecteds.map(implicitly[Numeric[T2]].toDouble)
  for (i <- expecteds.indices) {
    assert(Math.abs(ad(i) - ed(i)) < 1E-10)
  }
}

def assertCloseEnough[T1, T2](actuals: Seq[T1], expecteds: Seq[T2])(implicit t1: Numeric[T1], t2: Numeric[T2]): Unit = {
  assert(actuals.size == expecteds.size)
  val ad = actuals.map(_.toDouble)
  val ed = expecteds.map(_.toDouble)
  for (i <- expecteds.indices) {
    assert(Math.abs(ad(i) - ed(i)) < 1E-10)
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X