object Main {
val list = List[Long]()
val map1 = Map[Long, List[Long]]()
val map2 = Map[Long, Long]()
def main(args: Array[String]): Unit = {
for {
a: Long <- list
b: List[Long] <- map1.get(a)
c: Long <- b
d: Long <- map2.get(c)
} yield d
}
}
The type of a,b,c,d has been declared just for easy to analysis the code.
Then I get the unexpected compilation error:
Error:(10, 15) type mismatch;
found : List[Long]
required: Option[?]
c: Long <- b
The type of b is a List, I think c should mean each item in the List b
Why the compiler expect b should be a Option something?
The problem is that you are mixing options with lists. If you convert your option to a List it works:
b: List[Long] <- map1.get(a).toList
Remember that a for comprehension is just syntactic sugar for flatMap. This is actually performing a flatMapon the result of map1.get(a):
b: List[Long] <- map1.get(a)
c: Long <- b
which expects an option as a result. To avoid confusion you should always use the same type you started with, which is List in this case.
Another option would be to skip b entirely and directly flatten the result of the first map:
a: Long <- list
c: Long <- map1.get(a).toList.flatten
d: Long <- map2.get(c)
Related
I am not very versed in Scala and sincerily I found the documentation hard to try to figure this out, but I was wondering if someone could explain to me why the difference in compilation of the following statements:
I can easily iterate over a set of strings and yield the elements.
scala> for(name <- Set("asd", "123")) yield name
val res2: scala.collection.immutable.Set[String] = Set(asd, 123)
But i can't do it inline if the Set is inside an Option
scala> for(names <- Some(Set("asd", "123")); name <- names) yield (name)
^
error: type mismatch;
found : scala.collection.immutable.Set[String]
required: Option[?]
It happens because of for-yield is just syntactic sugar for flatMap, map and withFilter functions. So, your code:
for(names <- Some(Set("asd", "123")); name <- names) yield (name)
actually is the same as:
Some(Set("asd", "123")).flatMap{ names: Set[String] =>
names.flatMap{ name: String =>
name // return type is String, but Set[(of some )B] is expected
}
}// return type is Set[String] but Option[(of some) C] is expected
look at the Option flatMap function:
#inline final def flatMap[B](f: A => Option[B]): Option[B]
but your f returns Set[String]
as a result, compiler tell you about type missmatch (Set[String] != Option[?]):
error: type mismatch;
found : scala.collection.immutable.Set[String]
required: Option[?]
you should remember about type of the first statement in for-yield construction. In your case it's names <- Some(Set("asd", "123")). It has type Option[Set[String]], so you should use only Option[T] in x <- yourNextStatement lines (x should has Option[T] type).
In conclusion:
Be careful with mixing different container types in for-yield constructions. If you have some problems, just try to unwrap your for-yield into combination of flatMap, map, withFilter functions.
If you want to mix containers in for-yeild, you should start another for-yield for each-container type. For example:
for {
names <- Some(Set("asd", "123"))
// will return Option[String]
reducedNames <- (for {
name <- names // here will be used Seq flatMap function, not Option
} yield (name + "some_suffix"))
.reduceLeftOption(_ + _) // here we will get Option[String] from Seq[String]
} yield reducedNames // will return Option[String]
What do you want to happen for None? Assuming "nothing", you could do
val optionalSet = Some(Set("asd", "123"))
for (names <- optionalSet.getOrElse(Set.empty); name <- names) yield name
I mistakenly concatted a string with an Option[String] while coding in scala.
I expected as a strongly typed language, scala would not allow me to do such operation.
This is what I tried.
This works
scala> val a:String = "aaa"
val a: String = aaa
scala> val b:Option[String] = Some("bbbb")
val b: Option[String] = Some(bbbb)
scala> a + b
val res0: String = aaaSome(bbbb)
scala> val c:Option[String] = None
val c: Option[String] = None
scala> val d = a + c
val d: String = aaaNone
scala> val e = 1
val e: Int = 1
scala> a + e
val res2: String = aaa1
while this does not work
scala> val f:Option[String] = Some("ffff")
val f: Option[String] = Some(ffff)
scala> val g:Option[String] = None
val g: Option[String] = None
scala> f + g
^
error: type mismatch;
found : Option[String]
required: String
Why does scala allow such behavior? Dynamically typed languages like python will stop me from adding strings to int types, None types or any type other than strings. Curious if this design is intentional? If so why?
Scala contains an implicit class any2stringadd in it's Predef package. This class is responsible for these concatenation operations.
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
What it means is that, by default, scope contains a method + which can concatenate value of any type A with string by converting value of this type to string via String.valueOf(...).
I can't speak of design choices, I agree that this might be an unexpected behavior. The same applies to Scala's native == method. For example, this code compiles just ok: Some("a") == "b". This can lead to nasty bugs in filtering and other methods.
If you want to eliminate this behavior I suggest you take a look at https://typelevel.org/cats/ library, which introduces different typeclasses that can solve this problem.
For example, for string concatenation you can use Semigroup typeclass (which has tons of other useful use-cases as well):
import cats.Semigroup
Semigroup[String].combine("a", "b") // works, returns "ab"
Semigroup[String].combine("a", Some("b")) // won't work, compilation error
This looks tedious, but there is a syntactic sugar:
import cats.implicits._
"a" |+| "b" // works, returns "ab"
"a" |+| Some("b") // won't work, compilation error
// |+| here is the same as Semigroup[String].combine
The same thing applies to == method. Instead you can use Eq typeclass:
import cats.implicits._
"a" == Some("b") // works, no error, but could be unexpected
"a" === Some("b") // compilation error (Cats Eq)
"a" === "b" // works, as expected
"a" =!= "b" // same as != but type safe
I'm reading the book and playing with examples there. The problem is I can't reproduce this example:
type Logged[A] = Writer[List[String], A]
// Example method that returns nested monads:
def parseNumber(str: String): Logged[Option[Int]] =
util.Try(str.toInt).toOption match {
case Some(num) => Writer(List(s"Read $str"), Some(num))
case None => Writer(List(s"Failed on $str"), None)
}
// Example combining multiple calls to parseNumber:
def addNumbers(
a: String,
b: String,
c: String
): Logged[Option[Int]] = {
import cats.data.OptionT
// Transform the incoming stacks to work on them:
val result = for {
a <- OptionT(parseNumber(a))
b <- OptionT(parseNumber(b))
c <- OptionT(parseNumber(c))
} yield a + b + c
// Return the untransformed monad stack:
result.value
}
Compiling errors:
Error:(30, 9) could not find implicit value for parameter F: cats.Monad[advancedScalaWithCats.monadTransformation.Part4UsagePatterns.Logged]
b <- OptionT(parseNumber(b))
Error:(30, 9) not enough arguments for method flatMap: (implicit F: cats.Monad[advancedScalaWithCats.monadTransformation.Part4UsagePatterns.Logged])cats.data.OptionT[advancedScalaWithCats.monadTransformation.Part4UsagePatterns.Logged,Int].
Unspecified value parameter F.
b <- OptionT(parseNumber(b))
Add
import cats.instances.list._
I'm currently stacking Futures and Eithers using EitherT:
type ErrorOr[A] = Either[Error, A]
def getAge: Future[ErrorOr[Int]] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- EitherT(getAge)
dob <- EitherT.fromEither[Future](getDob(age))
} yield dob
I would now like to introduce the Writer monad i.e.
type MyWriter[A] = Writer[Vector[String], ErrorOr[A]]
def getAge: Future[MyWriter[Int]] = ???
def getDob(age: Int): MyWriter[LocalDate] = ???
My question is, what is the best way to sequence the getAge and getDob calls? I know monads can be stacked i.e. Future -> Writer -> Either but can I continue to use EitherT in this scenario? if so how?
Yeah, you can continue using both using the WriterT monad transformers like this:
type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
If you unpack this type, it's analogous to Future[Either[Error, Writer[Vector[String], A]]
Now the tricky part is lifting your functions into this base monad, so here are some examples:
def getAge: FutureErrorOr[Int] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- WriterT.liftF(getAge)
dob <- WriterT.liftF(EitherT.fromEither(getDob(age)))
} yield dob
To make this easier you can have a look at cats-mtl.
This is a slight variation to the approach given by #luka-jacobowitz. With his approach, any logs that occurred right up until a "failure" will be lost. Given the suggested types:
type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
We find that if we expand a value of MyStack[A] with the run method of WriterT we get a value of the following type:
FutureErrorOr[(Vector[String], A)]
which is the same thing as:
EitherT[Future, Error, (Vector[String], A)]
which we can then expand further with the value method of EitherT:
Future[Either[Error, (Vector[String], A)]]
Here we can see that the only way to retrieve the tuple containing the logs of the result is if the program was "successful" (ie. right associative). If the program fails, any previous logs that were created while the program was running are inaccessible.
If we take the original example and modify it slightly to log something after each step and we assume that the second step returns a value of type Left[Error]:
val program = for {
age <- WriterT.liftF(getAge)
_ <- WriterT.tell(Vector("Got age!"))
dob <- WriterT.liftF(EitherT.fromEither(getDob(age))) // getDob returns Left[Error]
_ <- WriterT.tell(Vector("Got date of birth!"))
} yield {
dob
}
Then when we evaluate the result, we will only get back the left case containing the error without any logs:
val expanded = program.run.value // Future(Success(Left(Error)))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // Left(Error), no logs!!
In order to get the value that results from running our program AND the logs that were generated up until the point where the program failed, we can reorder the suggested monads like this:
type MyWriter[A] = WriterT[Future, Vector[String], A]
type MyStack[A] = EitherT[MyWriter, Error, A]
Now, if we expand MyStack[A] with the value method of EitherT we get a value of the following type:
WriterT[Future, Vector[String], Either[Error, A]]
which we can expand further with the run method of WriterT to give us a tuple containing the logs AND the resulting value:
Future[(Vector[String], Either[Error, A])]
With this approach, we can re-write the program like this:
val program = for {
age <- EitherT(WriterT.liftF(getAge.value))
_ <- EitherT.liftF(WriterT.put(())(Vector("Got age!")))
dob <- EitherT.fromEither(getDob(age))
_ <- EitherT.liftF(WriterT.put(())(Vector("Got date of birth!")))
} yield {
dob
}
And when we run it, we will have access to the resulting logs even if there is a failure during the program's execution:
val expanded = program.value.run // Future(Success((Vector("Got age!), Left(Error))))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // (Vector("Got age!), Left(Error))
Admittedly, this solution requires a bit more boilerplate, but we can always define some helpers to aid with this:
implicit class EitherTOps[A](eitherT: FutureErrorOr[A]) {
def lift: EitherT[MyWriter, Error, A] = {
EitherT[MyWriter, Error, A](WriterT.liftF[Future, Vector[String], ErrorOr[A]](eitherT.value))
}
}
implicit class EitherOps[A](either: ErrorOr[A]) {
def lift: EitherT[MyWriter, Error, A] = {
EitherT.fromEither[MyWriter](either)
}
}
def log(msg: String): EitherT[MyWriter, Error, Unit] = {
EitherT.liftF[MyWriter, Error, Unit](WriterT.put[Future, Vector[String], Unit](())(Vector(msg)))
}
val program = for {
age <- getAge.lift
_ <- log("Got age!")
dob <- getDob(age).lift
_ <- log("Got date of birth!")
} yield {
dob
}
Using Scala 2.9.1, consider the following two instances of Either:
scala> val er: Either[String, Int] = Right(1)
er: Either[String,Int] = Right(1)
scala> val el: Either[String, Int] = Left("a")
el: Either[String,Int] = Left(a)
It's great that, by using the left and right projections, one can use for-comprehensions (via biased monad of the projected Either):
scala> for { r <- er.right } yield r * 2
res6: Product with Either[String,Int] with Serializable = Right(2)
scala> for { r <- el.right } yield r * 2
res7: Product with Either[String,Int] with Serializable = Left(a)
Can someone explain to me why the decision was made not to have the filter method return an Either? I would have expected the following to work:
scala> for { r <- er.right if r > 2 } yield r * 2 // r is NOT greater than 2!
res8: Product with Either[String,Int] with Serializable = Left(a)
Instead you get the following error:
:9: error: value * is not a member of Either[Nothing,Int]
for { r <- er.right if r > 2 } yield r * 2
It appears that the underlying call in Either.RightProjection#filter actually returns an Option:
scala> er.right.filter(_ > 2)
res9: Option[Either[Nothing,Int]] = None
This defeats the use of the if clause in the for-comprehension, at least the way I was trying to use it.
Does anyone have an explanation of why this design is the way it is?
It boils down to the fact that if you have a Right(b), but your filter predicate fails, you have no value to put in a Left.
You might imagine an implementation that works for your case of Either[String, Int], by failing with a default value of Left(""). The Scala standard library doesn't have the facility to to produce a value for you, because it doesn't include a concept such as a monoid which would determine the "empty" value for a type.
The Scalaz library does include a monoid typeclass, and version 7 also includes a right-biased disjunction type, \/[A, B] (isomorphic to Either[A, B]) which has a filter method iff the left type is a monoid:
scala> \/.right[String, Int](1).filter(_ > 2)
res1: scalaz.\/[String,Int] = -\/()
But you couldn't do this for the general case - if you had an Either[Nothing, Int], you could never produce a left value.