In Scala, why can't I implement a trivial generic function like this?
我想要一个称为" double"的通用函数,其行为类似于此函数,并且可以通过
1 2 3 4 5 6 | double("A") >"AA" double(1) > 2 double(0.2) > 0.4 |
所以我这样写这个函数:
1 |
但是当我在REPL中运行它时,scala对此表示抱怨:
1 2 3 4 5 6 |
我认为结构类型可能是实现鸭子类型的一种方法,我尝试了类似的方法,但是它也不起作用:
1 2 |
有人对此有想法吗?谢谢!
我在Haskell中发现,类似的函数可以这样写:
1 | double x = x + x |
只是想知道为什么我不能在Scala中做到这一点...
并非每个类型
问题是,我们需要一种方法来提供证据证明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | trait CanDouble[A] { def double(a: A): A } // Create some instances for types we know, like String, or numeric types implicit val StringDouble: CanDouble[String] = new CanDouble[String] { def double(a: String): String = a + a } // Uses the Numeric type class to create a CanDouble for all Numeric types implicit def numericDouble[A: Numeric]: CanDouble[A] = { new CanDouble[A] { def double(a: A): A = implicitly[Numeric[A]].plus(a, a) } } |
现在我们可以定义
1 2 3 4 5 6 7 8 9 10 | def double[A: CanDouble](a: A): A = implicitly[CanDouble[A]].double(a) scala> double(1) res4: Int = 2 scala> double(0.4) res5: Double = 0.8 scala> double("a") res6: String = aa |
理想情况下,您会将所有类型类实例(例如
我认为结构类型在这里根本无法工作,因为不允许您在结构细化之外定义的结构细化中使用抽象类型参数(类型参数
Within a method declaration in a structural refinement, the type of any value parameter may only refer to type parameters or abstract types that are contained inside the refinement. That is, it must refer either to a type parameter of the method itself, or to a type definition within the refinement. This restriction does not apply to the method's result type.
通常应避免使用结构类型,因为它们非常慢。在这种情况下,应首选类型类。
在haskell中,您可以:
1 2 3 4 5 6 7 | Prelude> let double x = x + x // (1) Prelude> let quadruple x = double (double x) //(2) Prelude> :t double double :: Num a => a -> a Prelude> :t quadruple quadruple :: Num a => a -> a |
在scala中,您必须明确指定
1 2 3 4 5 |
因为haskell的类型推断更聪明。 (1)第一行确实找到了typeclass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Prelude> :info Num class Num a where (+) :: a -> a -> a //looks like structural types, but ... (*) :: a -> a -> a (-) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a -- Defined in a€?GHC.Numa€? //... but here is implementations found accross build - they are explicitly saying that they are instances of Num instance Num Integer -- Defined in a€?GHC.Numa€? instance Num Int -- Defined in a€?GHC.Numa€? instance Num Float -- Defined in a€?GHC.Floata€? instance Num Double -- Defined in a€?GHC.Floata€? |
Scala也存在结构类型问题-您不能定义多态结构类型(不仅如此-您不能定义多态lambda)"结构细化中的参数类型不能引用该细化之外定义的抽象类型"
否则
查看其他答案以了解其真正定义方式(
在第(2)行编译器中,从
1 2 3 4 5 6 7 8 9 10 11 | scala> trait Num[T]{ val a: T; def + (b: Num[T]): Num[T] } defined trait Num scala> implicit class NumInt(val a: Int) extends Num[Int] {override def + (b: Num[Int]) = NumInt(a + b.a)} defined class NumInt scala> def double[T](a: Num[T]) = a + a double: [T](a: Num[T])Num[T] scala> double(5) res4: Num[Int] = NumInt@424f5762 |
但是问题仍然存在-您必须在scala中指定输入类型(
但是,即使在Haskell中,您也不能说:
1 2 3 4 5 6 | Prelude> let double x = x +++ x <interactive>:28:18: Not in scope: a€?+++a€? Perhaps you meant a€?++a€? (imported from Prelude) Otherwise `Num` would be defined in Scala as something like that: |
Haskell的真实鸭子输入不太容易使用:http://chrisdone.com/posts/duck-typing-in-haskell
这是何时使用类型类的完美示例。
1 |
您不能,因为您不知道T是什么。
此处将按以下方式工作。您有一个名为Doubles:
的类型构造函数。
现在为了方便起见,在同伴对象中,我将按以下方式重写您的double函数:
因此,我可以将T加倍,只要范围内有T的Doubles,或者您明确为我提供了T的Doubles。否则,我将无法将T加倍,则会出现编译器错误。
以下将是该类型类Doubles [T]的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | object Implicits{ //Now, you wouldn't want to have to write this for //every kind of number. Scala already provides you with a numeric //typeclass. So here's a function that gives a Doubles[N] //whenever you ask for a Doubles[Numeric[T]], i.e. a Doubles for a //member of the Numeric typeclass: implicit def numDoubler[N](implicit num : Numeric[N]) : Doubles[N] = new Doubles[N]{ def double(n : N) : N = num.plus(n,n) } implicit object stringDoubler extends Doubles[String]{ def double(t : String) : String = t + t } //So something like this is no longer needed: // implicit object intDoubler extends Doubles[Int]{ // def double(t : Int) : Int = t + t // } } |