Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use case and examples for type pattern with type variable

I found out reading the spec that scala supports binding type variables when doing a type pattern match:

Map(1 -> "one", 2 -> "two") match {
  case l: Map[k, v] =>
    // binds k to Int and v to String
    // k and v are types as shown here:
    val i: Iterator[Tuple2[k, v]] = l.iterator
    println(i.mkString(", "))
}

Are there any fancy things or practical things I can do with this? Or binding type variables is only useful for type documentation purpose?

It occurred to me that Scala sometimes needs type annotations, such as defining function, so I tried:

def prepender(obj: Any) = obj match {
  case xs: List[a] => (x: a) => x :: xs
  case opt: Some[a] => (x: a) => x :: Nil
}

But then the type of the return function is weird:

prepender: (obj: Any)a with a => List[Any] forSome { type a; type a }

scala> val p = prepender(List(1,2))
p: a with a => List[Any] forSome { type a; type a } = <function1>

scala> p(1)
<console>:10: error: type mismatch;
 found   : Int(1)
 required: a(in value res7) with (some other)a(in value res7) where 
   type (some other)a(in value res7), type a(in value res7)
like image 546
huynhjl Avatar asked Sep 06 '11 00:09

huynhjl


People also ask

Which method of case class allows using objects in pattern matching?

The match method takes a number of cases as an argument. Each alternative takes a pattern and one or more expressions that will be performed if the pattern matches.

How does pattern matching work in OCaml?

Pattern matching comes up in several places in OCaml: as a powerful control structure combining a multi-armed conditional, unification, data destructuring and variable binding; as a shortcut way of defining functions by case analysis; and as a way of handling exceptions.

How pattern matching works in a function's parameter list?

Pattern matching tests whether a given value (or sequence of values) has the shape defined by a pattern, and, if it does, binds the variables in the pattern to the corresponding components of the value (or sequence of values). The same variable name may not be bound more than once in a pattern.

What is pattern matching explain with example?

Pattern matching is the process of checking whether a specific sequence of characters/tokens/data exists among the given data. Regular programming languages make use of regular expressions (regex) for pattern matching.


1 Answers

I hope this won't get too long, but I seriously doubt it, that's why I'm gonna try to provide a quick answer first: "When you name (abstract) something, the main use case is referring to it later". Well that wasn't helpful now, was it?

Consider this simple Scala function:

val sum = (a: Int, b: Int) => a + b

The compiler does not need to know that a is an a and b is a b. All it needs to know that a as well as b are of type Int and that a comes before b (which wouldn't matter in this case since addition is commutative, but the compiler cares anyway!). Scala offers a (don't get me wrong I also love it) compiler friendly placeholder syntax, which acts as a proof of this "hypothesis".

val sum: (Int, Int) => Int = _ + _ // where the 1st _ differs from the 2nd _

Now take a look at this:

case x: SomeTypeParameterizedWith[AnotherType] // AnotherType is erased anyway
case x: SomeParameterizedType[_] // Existential type
case x: SomeParameterizedType[kind] // Existential type which you can reference

When you don't care about the type argument use the placeholder syntax. When you do (for whatever reason) care you should name the type argument with a lower case so the compiler knows you want to treat it as an identifier.

Back to your question.

The primary use for existential types is working around Java's wildcard types. This is taken from Programming in Scala - Existential Types and was slightly modified by yours truly.

// This is a Java class with wildcards
public class Wild {
  public java.util.Collection<?> contents() {
    java.util.Collection<String> stuff = new Vector<String>();
    stuff.add("a");
    stuff.add("b");
    stuff.add("see");
    return stuff;
  }
}

// This is the problem
import scala.collection.mutable.Set
val iter = (new Wild).contents.iterator
val set = Set.empty[???] // what type goes here?
while (iter.hasMore)
  set += iter.next()

// This is the solution
def javaSet2ScalaSet[T](jset: java.util.Collection[T]): Set[T] = {
  val sset = Set.empty[T] // now T can be named!
  val iter = jset.iterator
  while (iter.hasNext)
    sset += iter.next()
  sset
}

Ok, so what just happened? Simple generics, no magic there?! If you are dealing with generics on a day to day basis this looks normal to you, but you are forgetting, that the ultra super concept of introducing type arguments into scope works only on classes and methods. What if you are outside of a class or a method, just in some random scope in the middle of nowhere (like REPL)? Or what if you are in a class or a method but the type arguments have not been introduced into their scopes? This is where your question and this answer come in play.

val set = new Wild().contents match {
  case jset: java.util.Collection[kind] => {
    val sset = Set.empty[kind]
    val iter = jset.iterator
    while (iter.hasNext)
      sset += iter.next()
    sset
  }
}

The identifier kind is required so the compiler can verify that you are referring to the same thing.

Note, that you can't just add strings into the set since the type of the set is Set[_].

like image 142
agilesteel Avatar answered Sep 28 '22 17:09

agilesteel