简体   繁体   中英

Scala equivalent of 'forall a. Set a -> Set a -> Set a'

In haskell I can write a function f where

f :: Set a -> Set a -> Set a

and if I take two sets, s1 & s2 of type Set Int , and do f s1 s2 it would produce something of type Set Int .

In scala however, I cannot write this, because A is some fixed type, which conflicts with Long.

val x = Set(3L)
val y = Set(4L)

def foo[A](f: (Set[A], Set [A]) => Set [A]) = {
  f(x,y)
}

What I really want though is def foo[forall A. A] ... . How can I write this?

Edit The motivation is that I'm retrieving the data ( x & y ) from one source, and the method to call on them from another source. x & y are just some sets containing anything, but known to be the same type.
If I have some properly polymorphic function, I could just pass the x & y in, and intersection (or whatever) would work fine because intersection doesn't care what's in the sets, only that they're ordered. Perhaps I've gone forgotten how to do this in non haskell like ways...

In Scala and in Haskell type of f will be similar (up to an isomorphism):

f :: forall a. Set a -> Set a -> Set a
def f[A]: (Set[A], Set[A]) => Set[A]

Generic type parameters in Scala work exactly in the same way as type variables in Haskell. So I'm not sure why you say that in Scala it is impossible - it is not only possible but it even looks very similar. You can call f with arbitrary sets as arguments, just like you'd do it in Haskell:

f[Int](Set(1, 2), Set(3, 4))

The difference starts when you want to pass a polymorphic function into another function which will be able to use it with arbitrary type. In Haskell it requires higher-rank polymorphism:

foo :: (forall a. Set a -> Set a -> Set a) -> Whatever
foo f = toWhatever $ f (makeSet [1, 2, 3]) (makeSet [4, 5, 6])  // you get the idea

Scala does not have direct equivalent for this in its type system. You need to do a special trick to encode required relationship between types. First, define an additional trait:

trait PolyFunction2[F[_], G[_], H[_]] {
  def apply[A](f: F[A], g: G[A]): H[A]
}

Then you need to extend this trait to define polymorphic functions:

def f = new PolyFunction2[Set, Set, Set] {
  def apply[A](f: Set[A], g: Set[A]): Set[A] = f ++ g
}

And you need to use this trait to define type parameters:

def foo(f: PolyFunction2[Set, Set, Set]): (Set[Int], Set[String]) =
  (f(Set(1, 2), Set(3, 4)), f(Set("a"), Set("b")))

scala> foo(f)
res1: (Set[Int], Set[String]) = (Set(1, 2, 3, 4),Set(a, b))

Of course, this is an ad-hoc implementation, so you better use Shapeless as it is more general.

Here's a polymorphic function that computes the intersection of two sets of any type, using shapeless

import shapeless._
import shapeless.poly._

object intersect extends Poly2 {                                                                          
   implicit def caseSet[A] = at[Set[A], Set[A]] { case (set1, set2) => set1 & set2 }
}

f(Set(3L, 4L), Set(4L, 5L)) // Set(4)

f(Set("foo", "bar", "baz"), Set("bar", "baz", "faz")) // Set("bar", "baz")

Then you can define a method taking any polymorphic function that can operate on two Set s:

def foo[A](a: Set[A], b: Set[A], f: Poly2)(
  implicit c: Case2[f.type, Set[A], Set[A]]
) = f(a, b)

f(Set(3L, 4L), Set(4L, 5L), intersect) // Set(4)

f(Set("foo", "bar", "baz"), Set("bar", "baz", "faz"), intersect) // Set("bar", "baz")

That being said, the above is neat, but probably overkill in your case. In pure vanilla scala, you could instead do

def foo[A](a: Set[A], b: Set[A])(f: Function2[Set[A], Set[A], Set[A]]) = f(a, b)

foo(Set(1L, 2L), Set(2L, 3L)){ case (s1, s2) => s1 & s2 } // Set(2)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM