I wrote the following use case in scala:
val wordShortcut = Map("volume" -> "vol", "report" -> "rpt", ...)
object WordShortcutCase {
def unapply(key: String): Option[String] = wordShortcut.get(key)
}
val pluralR = "(.+)s".r
def encodeToken(token: String) = token match {
case WordShortcutCase(short) => short
case pluralR(singular) => singular
case _ => token
}
if scala Map would implement unapply, I wouldn't need the extra WordShortcutCase object (i could use case wordShortcut(short) => short instead`). This seems a common pattern to me.
And so the question is why scala Map does not implement the unapply method?
Map doesn't implement unapply because there is no sensible implementation that has the same characteristics as other collections.
In particular, you seem to want apply and unapply to do basically the same thing. But that's not how other collections work; they bind variables to contents and expect that the list is exhaustive (in the absence of a binding to "the rest"):
val xs = List("fish")
val ys = List("fish", "dish")
def iam(zs: List[String]) = zs match {
case List(x) => println(s"I am a $x")
case _ => println("Who am I??")
}
iam(xs) // Prints 'I am a fish'
iam(ys) // Prints 'Who am I??'
If Map were not a collection it would be free to implement unapply as another way to do an apply, more like regex does (though there, note that the key feature is being able to bind multiple variables to parts of the regex match). But since it is, having a regex-like unapply would be highly confusing because of the difference from other collections; and because maps are unordered and unapplySeq is ordered, having the same unapply as other collections would also be confusing. So it just doesn't have one.
Related
I am following this tutorial on GraphQL with Sangria. I am wondering about the following line
val JsObject(fields) = requestJSON
where requestJSON is an object of JsValue. This way of assigning fields is new to me and my question is, if you could name that pattern or provide me with a link to a tutorial regarding this structure.
The important thing to know is that val definitions support a Pattern on the left-hand side of the assignment, thus providing (subset of the functionality of) Pattern Matching.
So, your example is equivalent to:
val fields = requestJSON match {
case JsObject(foo) => foo
}
See Scala Language Specification Section 4.1 Value Declarations and Definitions for details.
So, for example, if you have a list l and you want to assign the first element and the rest, you could write:
val x :: xs = l
Or, for the fairly common case where a method returns a tuple, you could write:
val (result1, result2) = foo()
It is the Extractor pattern, you can reach the same result implementing the unapply method on your arbitrary object (like shown in the example). When you create a case class the compiler produces an unapply method for you, so you can do:
case class Person(name : String, surname : String)
val person = Person("gianluca", "aguzzi")
val Person(name, surname) = person
I have a class Foo extends Bar and a List or other collection of base class:
val bars: Iterable[Bar]
I need to extract all Foo elements from the collection. The following is the code:
val fooes: Iterable[Foo] = bars
.filter(x => Try(x.isInstanceOf[Foo]).isSuccess))
.map(_.isInstanceOf[Foo])
Is there conciser approach?
val fooes: Iterable[Foo] = bars.collect{case foo:Foo => foo}
The .collect() method takes a partial-function as its parameter. In this case the function is defined only for Foo types. All others are ignored.
Couple of possible rewrites worth remembering in general
filter followed by map as collect
isInstanceOf followed by asInstanceOf as pattern match with typed pattern
Hence the following discouraged style
bars
.filter { _.isInstanceOf[Foo] }
.map { _.asInstanceOf[Foo] }
can be rewritten to idiomatic style
bars collect { case foo: Foo => foo }
...writing type tests and casts is rather verbose in Scala. That's
intentional, because it is not encouraged practice. You are usually
better off using a pattern match with a typed pattern. That's
particularly true if you need to do both a type test and a type cast,
because both operations are then rolled into a single pattern match.
Note the nature of typed pattern is still just runtime type check followed by runtime type cast, that is, it merely represent nicer stylistic clothing not an increase in type safety. For example
scala -print -e 'lazy val result: String = (42: Any) match { case v: String => v }'
expands to something like
<synthetic> val x1: Object = scala.Int.box(42);
if (x1.$isInstanceOf[String]()) {
<synthetic> val x2: String = (x1.$asInstanceOf[String]());
...
}
where we clearly see type check isInstanceOf followed by type cast asInstanceOf.
I had spent weeks on trying to understand the idea behind "lifting" in scala.
Originally, it was from the example related to Chapter 4 of book "Functional Programming in Scala"
Then I found below topic "How map work on Options in Scala?"
The selected answer specify that:
def map[B](f: A => B): Option[B] = this match (Let's considered this as (*) )
So, from above code, I assume that function "map" is derived from function match. Hence, the mechanism behind "map"
is a kind of pattern matching to provide a case selection between Some, and None
Then, I created below examples by using function map for Seq, Option, and Map (Let's considered below examples as (**) )
Example 1: map for Seq
val xs = Seq(1, 2, 3)
xs.map(println)
Example 2: map for Option
val a:Option[Int] = Some(5)
a.map(println)
val b:Option[Int] = None
b.map(println)
Example 3: map for Map
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo")
capitals.map(println)
From (*) and (**), I could not know whether "map" is a pattern matching or an iteration, or both.
Thank you for helping me to understand this.
#Jwvh provided a more programming based answer but I want to dig a little bit deeper.
I certainly appreciate you trying to understand how things work in Scala, however if you really want to dig that deep, I am afraid you will need to obtain some basic knowledge of Category Theory since there is no "idea behind lifting in scala" but just the "idea behind lifting"
This is also why functions like "map" can be very confusing. Inherently, programmers are taught map etc. as operations on collections, where as they are actually operations that come with Functors and Natural Transformations (this is normally referred to as fmap in Category Theory and also Haskell).
Before I move on, the short answer is it is a pattern matching in the examples you gave and in some of them it is both. Map is defined specifically to the case, the only condition is that it maintains functoriality
Attention: I will not be defining every single term below, since I would need to write a book to build up to some of the following definitions, interested readers are welcome to research them on their own. You should be able to get some basic understanding by following the types
Let's consider these as Functors, the definition will be something along the lines of this:
In (very very) short, we consider types as objects in the category of our language. The functions between these types (type constructors) are morphisms between types in this category. The set of these transformations are called Endo-Functors (take us from the category of Scala and drop us back in the category of Scala). Functors have to have a polymorphic (which actually has a whole different (extra) definition in category theory) map function, that will take some object A, through some type constructor turn it into object B.
implicit val option: Functor[Option] = new Functor[Option] {
override def map[A,B](optA: Option[A])(f: (A) => B): Option[B] = optA match{
case Some(a) => Some(f(a))
case _ => None
}
}
implicit val seq: Functor[Seq[_]] = new Functor[Seq[_]] {
override def map[A,B](sA: Seq[A])(f: (A) => B): Seq[B] = sA match{
case a :: tail => Seq(f(a), map(tail)(f))
case Nil => Nil
}
}
As you can see in the second case, there is a little bit of both (more of a recursion than iteration but still).
Now before the internet blows up on me, I will say you cant pattern match on Seq in Scala. It works here because the default Seq is also a List. I just provided this example because it is simpler to understand. The underlying definition something along the lines of that.
Now hold on a second. If you look at these types, you see that they also have flatMap defined on them. This means they are something more special than plain Functors. They are Monads. So beyond satisfying functoriality, they obey the monadic laws.
Turns out Monad has a different kind of meaning in the core scala, more on that here: What exactly makes Option a monad in Scala?
But again very very short, this means that we are now in a category where the endofunctors from our previous category are the objects and the mappings between them are morphisms (natural transformations), this is slightly more accurate because if you think about it when you take a type and transform it, you take (carry over) all of it's internal type constructors (2-cell or internal morphisms) with it, you do not only take this sole idea of a type without it's functions.
implicit val optionMonad: Monad[Option] = new Monad[Option] {
override def flatMap[A, B](optA: Option[A])(f: (A) => Option[B]): Option[B] = optA match{
case Some(a) => f(a)
case _ => None
}
def pure[A](a: A): Option[A] = Some(a)
//You can define map using pure and flatmap
}
implicit val seqMonad: Monad[Seq[_]] = new Monad[Seq[_]] {
override def flatMap[A, B](sA: Seq[A])(f: (A) => Seq[B]): Seq[B] = sA match{
case x :: xs => f(a).append(flatMap(tail)(f))
case Nil => Nil
}
override def pure[A](a: A): Seq[A] = Seq(a)
//Same warning as above, also you can implement map with the above 2 funcs
}
One thing you can always count on is map being having pattern match (or some if statement). Why?
In order to satisfy the identity laws, we need to have some sort of a "base case", a unit object and in many cases (such as Lists) those types are gonna be what we call either a product or coproduct.
Hopefully, this did not confuse you further. I wish I could get into every detail of this but it would simply take pages, I highly recommend getting into categories to fully understand where these come from.
From the ScalaDocs page we can see that the type profile for the Standard Library map() method is a little different.
def map[B](f: (A) => B): Seq[B]
So the Standard Library map() is the means to transition from a collection of elements of type A to the same collection but the elements are type B. (A and B might be the same type. They aren't required to be different.)
So, yes, it iterates through the collection applying function f() to each element A to create each new element B. And function f() might use pattern matching in its code, but it doesn't have to.
Now consider a.map(println). Every element of a is sent to println which returns Unit. So if a is List[Int] then the result of a.map(println) is List[Unit], which isn't terribly useful.
When all we want is the side effect of sending information to StdOut then we use foreach() which doesn't create a new collection: a.foreach(println)
Function map for Option isn't about pattern matching. The match/case used in your referred link is just one of the many ways to define the function. It could've been defined using if/else. In fact, that's how it's defined in Scala 2.13 source of class Option:
sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable {
self =>
...
final def map[B](f: A => B): Option[B] =
if (isEmpty) None else Some(f(this.get))
...
}
If you view Option like a "collection" of either one element (Some(x)) or no elements (None), it might be easier to see the resemblance of how map transforms an Option versus, say, a List:
val f: Int => Int = _ + 1
List(42).map(f)
// res1: List[Int] = List(43)
List.empty[Int].map(f)
// res2: List[Int] = List()
Some(42).map(f)
// res3: Option[Int] = Some(43)
None.map(f)
// res4: Option[Int] = None
Is it good style in Scala to "abuse" the unapply method for pattern matching? What I wanted to do, is matching an object against another, and constructing the other object. Since I am fairly new in Scala, I wound up with the following solution. But it doesnt seem really right to use the unapply methode like this, since it is intended as an extractor. Could someone please give me feedback on this?
object Poker {
def unapply(hand: Hand): Option[Poker] = if(hand.countValueGroups().exists(_._2 == 4)) Some(new Poker(hand)) else None
}
val h = Hand("AC As AH Ad 2h")
h match {
case Poker(han) => println("POKER!!!"+han)
case _ => println("?????")
}
I don't know why this should be bad practice, so I'd say the answer is no, this is normal practice. As one commenter said, the mere purpose of unapply is to be used in pattern matching. While pattern matching is mostly used with the companion object of a case class, the concept is deliberately open to other extractors (example: regular expressions).
The only thing that's weird in your example is to return Option[Poker] with Poker begin a singleton object. Since you can't do much with that, probably you want to use a Boolean instead:
object Poker {
def unapply(hand: Hand): Boolean =
hand.countValueGroups().exists(_._2 == 4)
}
case class Hand(s: String) {
def countValueGroups(): List[(Any, Int)] = List("foo" -> 4) // ???
}
val h = Hand("AC As AH Ad 2h")
h match {
case Poker() => println("POKER!!!")
case _ => println("?????")
}
How is the head and tail determined in the following statement:
val head::tail = List(1,2,3,4);
//head: 1 tail: List(2,3,4)
Shouldn't there be some piece of code which extracts the first element as head and returns the tail as a new List. I've been combing through the Scala standard library code and I can't find/understand how/where this is done.
The Scala construct involved here is the Extractor. The symbol :: is nothing but a case class in Scala, where an an unapply method exists on its companion object to make the extraction magic happen. Here is a good in-depth tutorial on extractors. But here's the summary:
Whenever you want to "unpack" the contents of a class, either for variable binding or as a part of pattern matching, the compiler looks for the method unapply on whatever symbol is on the left hand side of the expression. This may be an object, a case class companion object (like ::, in your question), or an instance with an unapply. The argument to unapply is the incoming type to unpack, and the return type is an Option of what has been declared as the expected structure and types. In pattern matching a None indicates a match was not found. In variable binding a MatchError is thrown if None is the result.
A good way of thinking about unapply is that it is the inverse of apply. Where unapply the receiver of function-call syntax, unapply is the receiver of extractor calls.
To illustrate this further, let's define a simple case class:
case class Cat(name: String, age: Int)
Because it's a case class, we get automatically generated apply and unapply methods on the companion object, which roughly look like this:
object Cat {
// compiler generated...
def apply(name: String, age: Int) = new Cat(name, age)
def unapply(aCat: Cat): Option[(String, Int)] = Some((aCat.name, aCat.age))
}
When you create a Cat via the companion object, apply is called. When you unpack the constituent parts of a Cat, unapply is called:
val mycat = Cat("freddy", 3) // `apply` called here
...
val Cat(name, age) = mycat // `unapply` called here
...
val animal: AnyRef = mycat
val info = animal match {
case Cat(name, age) => "My pet " + name // `unapply` called here
case _ => "Not my pet"
}
// info: String = My pet freddy
Because unapply returns an Option, we have a lot of power to write extractors that handle more interesting cases, for instance, testing whether the incoming type conforms to some criteria before extracting values. For example, let's say we want to get the name of cats that are "old". One might do this:
object OldCatName {
def unapply(aCat: Cat) = if (aCat.age >= 10) Some(aCat.name) else None
}
Usage would be the same as a generated unapply:
val yourcat = Cat("betty", 12)
...
val OldCatName(name1) = yourcat
// name1: String = "betty"
val OldCatName(name2) = mycat
// scala.MatchError: Cat(freddy,3) (of class Cat)
MatchErrors aren't a nice thing to allow, so let's use pattern matching:
val conditions = Seq(mycat, yourcat) map {
case OldCatName(oldie) => s"$oldie is old"
case Cat(name, age) => s"At age $age $name is not old"
}
// conditions: Seq[String] = List(At age 3 freddy is not old, betty is old)
The one extra bit of magic involved with the unapply method for :: is that some syntactic sugar allows val ::(head, tail) = ... to be written val head :: tail = ... instead.