class OptionLikeMatcher[F[_], T, U](typeName: String, toOption: F[T] => Option[U]) extends Matcher[F[T]] { ... }
case class RightMatcher[T]() extends OptionLikeMatcher[({type l[a]=Either[_, a]})#l, T, T]("Right", (_:Either[Any, T]).right.toOption)
case class LeftMatcher[T]() extends OptionLikeMatcher[({type l[a]=Either[a, _]})#l, T, T]("Left", (_:Either[T, Any]).left.toOption)
Can some one explain what are they trying to achieve here with the type parameters?
What type does the toOption: F[T] => Option[U] parameter have when used for RightMatcher and LeftMatcher?
Its from Scala Specs2 library.
Let's first address the most complicated type constructor here:
is the original way to encode type lambdas. It could be written more succinctly as either
with kind-projector, or as
in Scala 3. It basically just selects the right parameter of
Eitheras the relevant one, and replaces the left parameter by a wildcard.Now, the three type parameters of
OptionLikeMatcherare:F[_]- the type constructor for the thing that's being matched; It is expected to behave "somewhat similarly" toOption, i.e. there should be some way to get anOptionout of it.Tis the type that is plugged into theFtype constructor; TogetherF[T]gives the type of what's being matched.Ucan be thought as a "slightly perturbed" version ofT; It captures basically the same information asT, but it allows for some controlled sloppiness when transforming anF[T]into anOption[U](otherwise, if one requiredF[T] => Option[T], that would practically be the same as asking for quite a rigid structure called natural transformationF ~> Option, which might be too restrictive in this case).Putting it all together gives for
RightMatcher:F[A] = Either[_, A]- i.e. pick right parameter ofEither, ignore left parameterTfrom theOptionLikeMatcheris bound toTfromRightMatcherUfrom theOptionLikeMatcheris also bound toTfromRightMatcher, i.e. the possibility to "slightly perturb"Tis not used here, bothTandUfromOptionLikeMatcherare instantiated by the same type.Thus, the
toOptioninRightMatcherhas the typeEither[_, T] => Option[T]. The(_:Either[Any, T]).right.toOptionconforms toEither[_, T] => Option[T], because theAnyappears in a covariant position withinEither, andEither[Any, T]appears in a contravariant position within the function type, so thatEither[Any, T] => Option[T]is a subtype ofEither[_, T] => Option[T], because its input type is strictly more permissive, and it can be used everywhere anEither[_, T] => Option[T]is needed.All that being said, I couldn't find any applications of
Fthat were not of shapeF[T](neither inOptionLikeMatcher, nor inOptionLikeChekedMatcher), so it seems that they are splitting up the input type into a type constructorFand a type parameterT, only to use them as an indivisible unitF[T]later on. Maybe this was left over after a refactoring from some more complicated construct.