Shapeless' HListOps includes a number of useful functions for their heterogeneous HList type. I couldn't find an equivalent for HMap.
Here is my goal. I have a simple Map[String, String] which is used as an options repository in the pipeline of message processing in quite a few places of my application. I now would like to add a different (Key => Value) to this map, by transforming it to a HMap, so it could be something like:
class HOptionsMap[K, V]
implicit val intToString = new HOptionsMap[String, String]
implicit val stringToInt = new HOptionsMap[String, Instant]
So I could further use it as follows:
val hm = HMap[HOptionsMap]("placeOfIncident" -> "Toronto", "incidentDate" -> Instant.now)
Except I would like to call operations like collect, fold, filter on the above, which are not supported (unlike with HList). This is a requirement in order to not break current functionality.
Of course I could use composition here but I would be curious if this would be achievable with Shapeless library.
If HMap is an inconvenient abstraction for your use case try to use a record
import shapeless.syntax.singleton._
val hm = "placeOfIncident" ->> "Toronto" :: "incidentDate" ->> Instant.now :: HNil
You can collect, fold, filter it as any HList/record.
https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#extensible-records
Related
I have been jumping through hoops trying to solve a problem with cats Monad.
Given the type constructor F[_] for which we have the implict F: Monad[F]
If we have the type List[F[Option[T]]] is it possible to flatten this type such that we end up with List[F[T]]
In effect I would like to filter any instances in the list where the inner Option is None.
Ultimately I want to end up with F[List[T]] which I can achieve using sequence on the partially flattened List[F[T]] but would be very interested in any way of achieving this in a single operation.
Cheers
Terry
Have a look at cats.FunctorFilter which provides a method mapFilter(F[A])(A => Option[B]): F[B]
val lfos: List[F[Option[String]]] = List(F.pure(Some("a")))
val lfs: List[F[String]] = lfos.map(_.mapFilter(identity))
val fls1: F[List[String]] = lfs.sequence
val fls2: F[List[String]] = lfos.traverse(_.mapFilter(identity))
val fls3: F[List[String]] = lfos.traverseFilter(identity)
I have class with field of type Set[String]. Also, I have list of objects of this class. I'd like to collect all strings from all sets of these objects into one set. Here is how I can do it already:
case class MyClass(field: Set[String])
val list = List(
MyClass(Set("123")),
MyClass(Set("456", "798")),
MyClass(Set("123", "798"))
)
list.flatMap(_.field).toSet // Set(123, 456, 798)
It works, but I think, I can achieve the same using only flatMap, without toSet invocation. I tried this, but it had given compilation error:
// error: Cannot construct a collection of type Set[String]
// with elements of type String based on a collection of type List[MyClass].
list.flatMap[String, Set[String]](_.field)
If I change type of list to Set (i.e., val list = Set(...)), then such flatMap invocation works.
So, can I use somehow Set.canBuildFrom or any other CanBuildFrom object to invoke flatMap on List object, so that I'll get Set as a result?
The CanBuildFrom instance you want is called breakOut and has to be provided as a second parameter:
import scala.collection.breakOut
case class MyClass(field: Set[String])
val list = List(
MyClass(Set("123")),
MyClass(Set("456", "798")),
MyClass(Set("123", "798"))
)
val s: Set[String] = list.flatMap(_.field)(breakOut)
Note that explicit type annotation on variable s is mandatory - that's how the type is chosen.
Edit:
If you're using Scalaz or cats, you can use foldMap as well:
import scalaz._, Scalaz._
list.foldMap(_.field)
This does essentially what mdms answer proposes, except the Set.empty and ++ parts are already baked in.
The way flatMap work in Scala is that it can only remove one wrapper for the same type of wrappers i.e. List[List[String]] -> flatMap -> List[String]
if you apply flatMap on different wrapper data types then you will always get the final outcome as higher level wrapper data type i.e.List[Set[String]] -> flatMap -> List[String]
if you want to apply the flatMap on different wrapper type i.e. List[Set[String]] -> flatMap -> Set[String] in you have 2 options :-
Explicitly cast the one datatype wrapper to another i.e. list.flatMap(_.field).toSet or
By providing implicit converter ie. implicit def listToSet(list: List[String]): Set[String] = list.toSet and the you can get val set:Set[String] = list.flatMap(_.field)
only then what you are trying to achieve will be accomplished.
Conclusion:- if you apply flatMap on 2 wrapped data type then you will always get the final result as op type which is on top of wrapper data type i.e. List[Set[String]] -> flatMap -> List[String] and if you want to convert or cast to different datatype then either you need to implicitly or explicitly cast it.
You could maybe provide a specific CanBuildFrom, but why not to use a fold instead?
list.foldLeft(Set.empty[String]){case (set, myClass) => set ++ myClass.field}
Still just one pass through the collection, and if you are sure the list is not empty, you could even user reduceLeft instead.
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
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.
The Scala Collection library has mapValues and filterKeys. The reason it doesn't have mapKeys is likely the performance aspect (with regard to HashMap implementation), as discussed here for Haskell: Why there's not mapKeys in Data.Hashmap?
However.
Performance implications aside, I find myself needing mapKeys at least as much as mapValues, simply for massaging data (i.e. I'm using a map for data abstraction, not for its fetch speed).
Am I wrong, and which data model would you use for this? Tuples?
No idea why it's not in standard library, but you can easily pimp your library with implicit class
implicit class MapFunctions[A, B](val map: Map[A, B]) extends AnyVal {
def mapKeys[A1](f: A => A1): Map[A1, B] = map.map({ case (a, b) => (f(a), b) })
}
val m = Map(1 -> "aaa", 2 -> "bbb")
println(m.mapKeys(_ + 1))
You can use scalaz:
import scalaz.Scalaz._
val m = Map(1 -> "aaa", 2 -> "bbb")
m.mapKeys(_ + 1)
In case of collisions the result may be smaller than the original Map.
I guess it might be related to collision resolution.
Different keys after map may get the same values, e.g. "Key"=>key, "KEY"=>key
It is unclear how to resolve this type of conflicts without changing the value type (Set?) or overriding values which might be undesirable.