scala lazy zip of varargs options to option of tuple - scala

i want to combine multiple options to an option of a tuple:
val maybeA: Option[Int] = ...
val maybeB: Option[String] = ...
val combined: Option[(Int,String)] = combine(maybeA, maybeB)
there are many ways to do this, let's go with:
def combined[X,Y](maybeA: Option[X], maybeB: Option[Y]) = maybeA.zip(maybeB).headOption
and that's great.
now i wonder, can this be done for varargs and a tuple dynamically somehow?
what would the signature look like?
def combine[???](options: Option[?]*): Tuple[?]

Related

How to generalise implementations of 'Seq[String] => Seq[Int]' and 'Iterator[String] => Iterator[Int]' for file processing?

Suppose I've got a function Seq[String] => Seq[Int], e.g. def len(as: Seq[String]): Int = as.map(_.length). Now I would like to apply this function to a text file, e.g. transform all the file lines to numbers.
I read a text file as scala.io.Source.fromFile("/tmp/xxx.txt").getLines that returns an iterator.
I can use toList or to(LazyList) to "convert" the iterator to Seq but then I read the whole file into the memory.
So I need to write another function Iterator[String] => Iterator[Int], which is actually a copied version of Seq[String] => Seq[Int]. Is it correct ? What is the best way to avoid the duplicated code?
If you have an arbitrary function Seq[String] => Seq[Int], then
I use toList or to(LazyList) to "convert" the iterator to Seq but in both cases I read the whole file in the memory.
is the best you can do, because the function can start by looking at the end of the Seq[String], or its length, etc.
And Scala doesn't let you look "inside" the function and figure out "it's map(something), I can just do the same map for iterators" (there are some caveats with macros, but not really useful here).
So I need to write another function Iterator[String] => Iterator[Int], which is actually a copied version of Seq[String] => Seq[Int]. Is it correct ? What is the best way to avoid the duplicated code?
If you control the definition of the function, you can use higher-kinded types to define a function which works for both cases. E.g. in Scala 2.13
def len[C[A] <: IterableOnceOps[A, C, C[A]]](as: C[String]): C[Int] = as.map(_.length)
val x: Seq[Int] = len(Seq("a", "b"))
val y: Iterator[Int] = len(Iterator("a", "b"))

Combine valid values of Seq[ValidatedNel]

I have the following scenario:
case class MyString(str: String)
val val1: ValidatedNel[String, MyString] = MyString("valid1").validNel
val val2: ValidatedNel[String, MyString] = MyString("valid2").validNel
val val3: ValidatedNel[String, MyString] = "invalid".invalidNel
val vals = Seq(val1, val2, val3)
//vals: Seq[cats.data.Validated[cats.data.NonEmptyList[String],MyString]] = List(Valid(MyString(valid)), Invalid(NonEmptyList(invalid)))
At the end I'd like to be able to do a match on the result and get any and all errors or all the valid values as a sequence.
My question is: How to convert Seq[Validated[NonEmptyList[String],MyString]] into Validated[NonEmptyList[String],Seq[MyString]]]
So, my first pass was to implement Semigroup for Seq[MyString]:
implicit val myStringsAdditionSemigroup: Semigroup[Seq[MyString]] = new Semigroup[Seq[MyString]] {
def combine(x: Seq[MyString], y: Seq[MyString]): Seq[MyString] = x ++ y
}
... which works:
Seq(val1, val2).map(_.map(Seq(_))).reduce(_ |+| _)
//res0: cats.data.Validated[cats.data.NonEmptyList[String],Seq[MyString]] = Valid(List(MyString(valid1), MyString(valid2)))
but I need to prepare my data by wrapping all valid values in Seq... which feels strange. So, maybe there's a better way of doing that?
If you use anything other than Seq, like Vector or List, you can sequence it.
sequence basically turns a type constructor inside out. Meaning turning a F[G[A]] into an G[F[A]]. For that to work, the F needs to be a Traverse and the G needs to be an Applicative. Luckily, Validated is an Applicative and List or Vector are instances of Traverse.
So in the end your code should look something like this:
import cats.implicits._
val validatedList: Validated[NonEmptyList[String],List[MyString]]] =
vals.sequence
Note: if this doesn't compile for you, you might need to enable partial-unification.
The easiest way to enable partial-unification, is to add the sbt-partial-unification plugin.
If you're on Scala 2.11.9 or newer, you can also simply add the compiler flag:
scalacOptions += "-Ypartial-unification"
We from the cats team strongly encourage you to have this flag on at all times when using cats, as it makes everything just a lot easier.

How to make it a monad?

I am trying to validate a list of strings sequentially and define the validation result type like that:
import cats._, cats.data._, cats.implicits._
case class ValidationError(msg: String)
type ValidationResult[A] = Either[NonEmptyList[ValidationError], A]
type ListValidationResult[A] = ValidationResult[List[A]] // not a monad :(
I would like to make ListValidationResult a monad. Should I implement flatMap and pure manually or there is an easier way ?
I suggest you to take a totally different approach leveraging cats Validated:
import cats.data.Validated.{ invalidNel, valid }
val stringList: List[String] = ???
def evaluateString(s: String): ValidatedNel[ValidationError, String] =
if (???) valid(s) else invalidNel(ValidationError(s"invalid $s"))
val validationResult: ListValidationResult[String] =
stringList.map(evaluateString).sequenceU.toEither
It can be adapted for a generic type T, as per your example.
Notes:
val stringList: List[String] = ??? is the list of strings you want to validate;
ValidatedNel[A,B] is just a type alias for Validated[NonEmptyList[A],B];
evaluateString should be your evaluation function, it is currently just an unimplemented stub if;
sequenceU you may want to read cats documentation about it: sequenceU;
toEither does exactly what you think it does, it converts a Validated[A,B] to an Either[A,B].
As #Michael pointed out, you could also use traverseU instead of map and sequenceU
val validationResult: ListValidationResult[String] =
stringList.traverseU(evaluateString).toEither

Cats Seq[Xor[A,B]] => Xor[A, Seq[B]]

I have a sequence of Errors or Views (Seq[Xor[Error,View]])
I want to map this to an Xor of the first error (if any) or a Sequence of Views
(Xor[Error, Seq[View]]) or possibly simply (Xor[Seq[Error],Seq[View])
How can I do this?
You can use sequenceU provided by the bitraverse syntax, similar to as you would do with scalaz. It doesn't seem like the proper type classes exist for Seq though, but you can use List.
import cats._, data._, implicits._, syntax.bitraverse._
case class Error(msg: String)
case class View(content: String)
val errors: List[Xor[Error, View]] = List(
Xor.Right(View("abc")), Xor.Left(Error("error!")),
Xor.Right(View("xyz"))
)
val successes: List[Xor[Error, View]] = List(
Xor.Right(View("abc")),
Xor.Right(View("xyz"))
)
scala> errors.sequenceU
res1: cats.data.Xor[Error,List[View]] = Left(Error(error!))
scala> successes.sequenceU
res2: cats.data.Xor[Error,List[View]] = Right(List(View(abc), View(xyz)))
In the most recent version of Cats Xor is removed and now the standard Scala Either data type is used.
Michael Zajac showed correctly that you can use sequence or sequenceU (which is actually defined on Traverse not Bitraverse) to get an Either[Error, List[View]].
import cats.implicits._
val xs: List[Either[Error, View]] = ???
val errorOrViews: Either[Error, List[View]] = xs.sequenceU
You might want to look at traverse (which is like a map and a sequence), which you can use most of the time instead of sequence.
If you want all failing errors, you cannot use Either, but you can use Validated (or ValidatedNel, which is just a type alias for Validated[NonEmptyList[A], B].
import cats.data.{NonEmptyList, ValidatedNel}
val errorsOrViews: ValidatedNel[Error, List[View]] = xs.traverseU(_.toValidatedNel)
val errorsOrViews2: Either[NonEmptyList[Error], List[View]] = errorsOrViews.toEither
You could also get the errors and the views by using MonadCombine.separate :
val errorsAndViews: (List[Error], List[View]) = xs.separate
You can find more examples and information on Either and Validated on the Cats website.

Is there a way to get a Scala HashMap to automatically initialize values?'

I thought it could be done as follows
val hash = new HashMap[String, ListBuffer[Int]].withDefaultValue(ListBuffer())
hash("A").append(1)
hash("B").append(2)
println(hash("B").head)
However the above prints the unintuitive value of 1. I would like
hash("B").append(2)
To do something like the following behind the scenes
if (!hash.contains("B")) hash.put("B", ListBuffer())
Use getOrElseUpdate to provide the default value at the point of access:
scala> import collection.mutable._
import collection.mutable._
scala> def defaultValue = ListBuffer[Int]()
defaultValue: scala.collection.mutable.ListBuffer[Int]
scala> val hash = new HashMap[String, ListBuffer[Int]]
hash: scala.collection.mutable.HashMap[String,scala.collection.mutable.ListBuffer[Int]] = Map()
scala> hash.getOrElseUpdate("A", defaultValue).append(1)
scala> hash.getOrElseUpdate("B", defaultValue).append(2)
scala> println(hash("B").head)
2
withDefaultValue uses exactly the same value each time. In your case, it's the same empty ListBuffer that gets shared by everyone.
If you use withDefault instead, you could generate a new ListBuffer every time, but it wouldn't get stored.
So what you'd really like is a method that would know to add the default value. You can create such a method inside a wrapper class and then write an implicit conversion:
class InstantiateDefaults[A,B](h: collection.mutable.Map[A,B]) {
def retrieve(a: A) = h.getOrElseUpdate(a, h(a))
}
implicit def hash_can_instantiate[A,B](h: collection.mutable.Map[A,B]) = {
new InstantiateDefaults(h)
}
Now your code works as desired (except for the extra method name, which you could pick to be shorter if you wanted):
val hash = new collection.mutable.HashMap[
String, collection.mutable.ListBuffer[Int]
].withDefault(_ => collection.mutable.ListBuffer())
scala> hash.retrieve("A").append(1)
scala> hash.retrieve("B").append(2)
scala> hash("B").head
res28: Int = 2
Note that the solution (with the implicit) doesn't need to know the default value itself at all, so you can do this once and then default-with-addition to your heart's content.