Type-safety for Patternmatching on Parameters with Dependent Types in Scala - scala

I am currently working with a type hierarchy which represents a tree and an accompanying type hierarchy that represents steps of root-to-node paths in that tree. There are different kinds of nodes so different steps can be taken at each node. Therefore, the node types have a type member which is set to a trait including all valid steps. Take the following example:
// Steps
sealed trait Step
sealed trait LeafStep extends Step
sealed trait HorizontalStep extends Step
sealed trait VerticalStep extends Step
object Left extends HorizontalStep
object Right extends HorizontalStep
object Up extends VerticalStep
object Down extends VerticalStep
// The Tree
sealed trait Tree {
type PathType <: Step
}
case class Leaf() extends Tree {
override type PathType = LeafStep
}
case class Horizontal(left: Tree, right: Tree) extends Tree {
override type PathType = HorizontalStep
}
case class Vertical(up: Tree, down: Tree) extends Tree {
override type PathType = VerticalStep
}
In this example, given a tree the path Seq(Up, Right) would tell us to go to the "right" child of the "up" child of the root (assuming the tree's nodes have the fitting types). Of course, navigating the tree involves a bunch of code using PartialFunctions that is not shown in the example. However, in that process I want to provide a type-safe callback which is notified for every step that is taken and its arguments include both the step and the respective tree nodes.
My current approach is a function def callback(from: Tree, to: Tree)(step: from.PathType). That works without problems on the caller side, but I'm running into an issue when actually implementing such a callback that works with the data passed to it.
def callback(from: Tree, to: Tree)(step: from.PathType) = {
from match {
case f: Horizontal => step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
case _ => ()
}
}
In the function, the compiler is not convinced that Left and Right are of type from.PathType. Of course, I could simply add .asInstanceOf[f.PathType] and the code seems to work with that. However, the outer match gives us that f is of type Horizontal. We know that PathType of Horizontal objects is HorizontalStep and since f is identical to from we also know that from.PathType is of that same type. Finally, we can check that both Left and Right extend HorizontalStep. So, the above code should always be type-safe even without the cast.
Is that reasoning something the Scala compiler just does not check or am I missing a case where the types could differ? Is there a better way to achieve my goal of a type-safe callback? I'm using Scala 2.12.15

I'm afraid I do not have a thorough explanation on why this does not typecheck. (It seems as if for such dependent-typed parameters, the compiler just isn't able to apply the knowledge that was gained from pattern matching onto the other parameter (that was not explictly matched)).
Nevertheless, here is something that would work:
First, use a type parameter instead of a type member:
// (Steps as above)
// The Tree
sealed trait Tree[PathType <: Step]
// type alias to keep parameter lists simpler
// where we do not care..
type AnyTree = Tree[_ <: Step]
case class Leaf() extends Tree[LeafStep]
case class Horizontal(left: AnyTree, right: AnyTree) extends Tree[HorizontalStep]
case class Vertical(up: AnyTree, down: AnyTree) extends Tree[VerticalStep]
Then, this would fully typecheck:
def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
def horizontalCallback(from: Horizontal)(step: HorizontalStep) = {
step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
}
from match {
case f: Horizontal =>
horizontalCallback(f)(step)
case _ =>
()
}
}
Confusingly, the compiler would not be able to properly check the pattern match on the step if one places it directly in the outer match like this (Edit: that is only true for 2.12 where this does not give a "match may not be exhaustive" warning - with 2.13 or 3.2, this checks fine):
def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
from match {
case f: Horizontal =>
step match {
case Left => ??? // do useful stuff here
case Right => ??? // do other useful stuff here
}
case _ =>
()
}
}

Related

How do generic types work with inheritance in scala?

I'm trying to understand how generics work with inheritance in Scala.
I have the following code:
sealed trait Model {}
case class Model1() extends Model
case class Model2() extends Model
trait Repo[A <: Model] {
def doSomething(model: A)
}
class Repo1 extends Repo[Model1] {
override def doSomething(model: Model1): Unit = {
println("Model 1")
}
}
class Repo2 extends Repo[Model2] {
override def doSomething(model: Model2): Unit = {
println("Model 2")
}
}
object Play extends App {
def getModel(i: Int): Model =
i match {
case 1 => Model1()
case 2 => Model2()
case _ => throw new RuntimeException("model not found")
}
val model = getModel(1)
val repo = model match {
case _: Model1 => new Repo1
case _: Model2 => new Repo2
case _ => throw new RuntimeException("something went wrong")
}
repo.doSomething(model)
}
On the last line repo.doSomething(model) I get Type mismatch. Required: _.$1 Found: Model
According to this answer What is the correct way to implement trait with generics in Scala? if my repos classes extend the trait with the type should work.
I'm new to Scala and I'm trying to wrap my head around the type system, generics, implicit, upper/lower bounds ...
What is _.$1 type and how can I make this work? Thanks!
scala is statically typed, and the value model is of compile time type Model and repo of the compile time type Repo
So repo.doSomething is no further refined. The signature of doSomething says it'll take some subtype of Model of a parameter, but we don't know which one -- in other words, the compiler doesn't know that the type of model and the type of repo align.
To make them align, you have a few options.
Because you know that the types align because you constructed it in a way where you know more than the compiler, tell the compiler so
val castRepo = repo.asInstanceOf[Repo[Any]]
That turns off the safeties, and you tell scala "trust me, I know what I'm doing". This is fine to some extent when you know what you're doing, but people who really know what they're doing tend to not trust themselves to know better than the compiler, so a different solution that does retain type safety is probably better.
restructure the program so that things do align.
You could make a wrapper type for example, like so
case class Aligned[A <: Model](model: A, repo: Repo[A]) {
def doIt = repo.doSomething(model)
}
val aligned = model match {
case m: Model1 => Aligned(m, new Repo1)
case m: Model2 => Aligned(m, new Repo2)
case _ => throw new RuntimeException("something went wrong")
}
aligned.doIt
Within Aligned, scalac knows the Model type and the Repo type line up.
You don't even really need the instance method doIt; aligned.repo.doSomething(aligned.model) also works, because the compiler knows the A in aligned.repo and the A in aligned.model are both the same A in aligned.

Nested types in Scala

I have two questions about nested types in Scala.
Imagine I have this kinda trait;
trait ScanList[E] {
sealed trait Command
case object Recover extends Command
case class Remove(item: E) extends Command
sealed trait Event
case class Removed(item: E) extends Event
}
And now I want to write a generic trait over this like this (the problems are encoded in the pattern matches as comment):
trait ScanListProcessor[E] {
type SL = ScanList[E]
def process(msg: SL#Command) = {
msg match {
case u:SL#Remove => // how can instantiate SL#Removed here?
case SL#Recover => //cannot match on nested objects?
}
}
}
The reason for using a trait is that I can derive new implementations of ScanList. In this trait I also have operations like def shouldProcess(item: E): Boolean. For each implementation of ScanList[E] I would like to write generic behaviour like depicted above.
How can pattern match on nested object in a generic type?
Is it possible to instantiate from a type constructor? For example: SL#Removed? I guess it's the same having a generic parameter and trying to construct a value from that, type classes would solve this?
Nested traits, class, and objects in Scala act like inner classes in Java. Any instance of them that you create is associated with an instance of the parent class/trait that they are created from. So continuing from your example:
val sl1 = new ScanList[Int] {}
val sl2 = new ScanList[Int] {}
val r1 = sl1.Removed(1) // has type sl1.Removed
val r2 = sl2.Removed(2) // has type sl2.Removed
val rs = List(r1, r2) // has type List[ScanList[Int]#Removed]
You can pattern match on them like:
rs match {
case List(sl1.Removed(x), sl2.Removed(y)) => (x, y)
}
Pattern matching requires that you explicitly references the parent instance that the nested instance belongs to. Here, we separately match on sl1.Removed and sl2.Removed.
As for you second question, you cannot create a SL#Removed without having a parent instance available. With one, it is as simple as sl1.Removed(1).
Your ScanListProcessor could be rewritten to take the ScanList it operates on as a value:
class ScanListProcessor[E](val sl: ScanList[E]) {
def process(msg: sl.Command) = {
msg match {
case sl.Remove(x) => sl.Removed(x)
case sl.Recover => ???
}
}
}
But this is awkward, and nested types probably aren't needed here. If all you want to do is group some traits, classes etc. together in a namespace, then put them in a package or object instead of a trait or class. In this case you would need to move the type parameter E down onto Remove and Removed themselves:
object ScanList {
sealed trait Command
case object Recover extends Command
case class Remove[E](item: E) extends Command
sealed trait Event
case class Removed[E](item: E) extends Event
}

creating two related ASTs with sealed case classes in Scala

Whenever I've had to create an AST in Scala, I've used the abstract sealed trait/ case class pattern. It's worked really well so far, having compiler checked pattern matching is a big win.
However now I've hit a problem that I cant wrap my head around. What if I have 2 languages where one is a subset of the other? As a simple example consider a lambda calculus where every variable is bound, and another related language where the variables could be bound or free.
First language:
abstract sealed class Expression
case class Variable(val scope: Lambda, val name:String) extends Expression
case class Lambda(val v: Variable, val inner: Expression) extends Expression
case class Application(val function: Expression, val input: Expression) extends Expression
Second Language:
abstract sealed class Expression
case class Variable(val name:String) extends Expression
case class Lambda(val v: Variable, val inner: Expression) extends Expression
case class Application(val function: Expression, val input: Expression) extends Expression
Where the only change is the removal of scope from Variable.
As you can see there is a lot of redundancy. But because I'm using sealed classes, its hard to think of a good way to extend it. Another challenge to combining them would be that now every Lambda and Application needs to keep track of the language of its parameters, at the type level.
This example is not so bad because it is very small, but imagine the same problem for strict HTML/weak HTML.
The classical answer to this problem is to have a single general AST and an additional pass for validation. You'll have to live with ASTs that are well-formed syntactically, but won't pass validation (type-checking).
If you want to distinguish at the type level, the type-checking pass could produce a new AST. You might be able to use path-dependent types for that.
As a side-note, your example seems to have a cycle: to create a Lambda you need a Variable, but to create a Variable you need the outer Lambda.
When deciding how to generalize, it is sometimes helpful to think of an example function that would need to operate on the generalized structure. So, take some operation that you would want to perform on both bound and free trees. Take eta-reduction:
def tryEtaReduce(x: Expression): Option[Expression] =
x match {
case Lambda(v1, Application(f, v2: Variable)) if v1 == v2 => Some(f)
case _ => None
}
For the above function, a generalization like the following will work, although it has an obvious ugliness:
trait AST {
sealed trait Expression
type Scope
case class Variable(scope: Scope, name: String) extends Expression
case class Lambda(v: Variable, inner: Expression) extends Expression
case class Application(function: Expression, input: Expression) extends Expression
}
object BoundAST extends AST {
type Scope = Lambda
}
object FreeAST extends AST {
type Scope = Unit
}
trait ASTOps {
val ast: AST
import ast._
def tryEtaReduce(x: Expression): Option[Expression] =
x match {
case Lambda(v1, Application(f, v2: Variable)) if v1 == v2 =>
Some(f)
case _ =>
None
}
}

Chaining path-dependent types and instantiating them when they having different parameter lists in Scala

I'm experimenting with writing more statically type-safe code by implementing a simple card game. In this game, there are several unique cards and each card has a card-specific effect which may require additional parameters (e.g., a target for the effect). A player holds two cards and on their turn chooses to play one of them, causing that card's effect to take place.
Note: most of the details in this post are from trying it out in the REPL. I have a less statically type-safe implementation written but I want to make sure that what I want is feasible before diving completely into it.
Here are some relevant definitions:
trait CardEffectParams
case class OneTarget(player: Player) extends CardEffectParams
case class TwoTargets(player1: Player, player2: Player) extends CardEffectParams
// ...
trait Card {
// the parameters to use are specific to the card
type Params <: CardEffectParams
}
trait Hand {
case class CardInHand(card: Card) { /* with ctor not accessible from outside */ }
// a player can hold two cards
val card1: CardInHand
val card2: CardInHand
}
I want to delegate the choosing of which card to play to some strategy so I can see how different strategies compare. This is where I'm stuck: I want to limit the cards you can return to the ones in the Hand object passed in the parameters, which I can do by typing it as hand.CardInHand:
trait Strategy {
def choose(hand: Hand, gameState: GameState): hand.CardsInHand
}
But I also want to pass extra parameters: for example, one card might allow me to target just one player (e.g., skip their turn), but another might let me target two (e.g., swap their cards). These are modelled by CardEffectParams. So I want to return both hand.CardsInHand and a cardInHand.card.Params where cardInHand is the instance I'm returning, something like this:
/* NOT valid scala */
trait Strategy {
def choose(hand: Hand, gameState: GameState): (c: hand.CardsInHand, c.card.Params)
}
So the first question is, can this be done? How would you represent this relationship?
I'm also stuck on how to instantiate the CardEffectParams subclasses, since each one may have different parameter lists. My first thought is to do a pattern match, but this fails because the type of the match block is the common ancestor of all possible results:
case object CardA extends Card {
type Params = OneTarget
}
case object CardB extends Card {
type Params = TwoTargets
}
object RandomStrategy extends Strategy {
def choose(hand: Hand, gameState: GameState) = {
val card: Card = /* randomly pick card1 or card2 */
/* the type of the match block is CardEffectParams, not card.Params */
val param: card.Params = card match {
case CardA => OneTarget(...)
case CardB => TwoTargets(...)
}
}
}
My current idea is to define a factory method within each card object that takes an hlist of arguments from which it produces the correct type:
trait Card {
type Params <: CardEffectParams
type HListTypeOfParams = /* insert shapeless magic */
def create[L <: HListTypeOfParams](l: L): Params
}
from which I can then do the following?
// no idea if this works or not
val card: Card = ...
val params: card.Params = card match {
case c: CardA => c.create(1 :: HNil)
case c: CardB => c.create(1 :: 2 :: HNil)
}
But I feel like I've gone too far down the rabbit hole. Is what I want to achieve possible? Is it necessary? Do I need to dive so deep into typing to ensure static type safety or am I missing something really elementary?
For the first question, I would replace your tuple with a type that represents the relationship
trait CardAndParams {
type C <: Card
val card: C
val params: C#Params
}
def choose[R <: CardAndParams](hand: Hand, gameState: GameState)(
implicit helper: Helper {type Out = R}): R
You will need to use implicits like my Helper example to drive the actual strategy implementations and ensure the correct R is inferred. This is also the more usual way to do type-level computation:
sealed trait RandomStrategyHelper[C <: Card] {
def params(): C#Params
}
object RandomStrategyHelper {
implicit def forCardA = new RandomStrategyHelper[CardA] {
def params() = 1 :: HNil
}
implicit def forCardB = new RandomStrategyHelper[CardB] {
def params() = 1 :: 2 :: HNil
}
}
def randomParams[C <: Card](card: C)(implicit rsh: RandomStrategyHelper[C]) =
rsh.params()
But I guess you need a way to move from your randomly-generated card to a strongly typed one, and for that the pattern match seems appropriate, since it would be difficult to represent a random card at type level.
In general this kind of type-level programming is possible but hard in Scala - the language wasn't really designed for it. If you want to push this as far as it will go you may be better off using something like Idris.

Best way to use type classes with list parametrized with some base class, abstract class or trait

I think it would be easier to describe a problem with concrete example. Suppose I have have Fruit class hierarchy and Show type class:
trait Fruit
case class Apple extends Fruit
case class Orange extends Fruit
trait Show[T] {
def show(target: T): String
}
object Show {
implicit object AppleShow extends Show[Apple] {
def show(apple: Apple) = "Standard apple"
}
implicit object OrangeShow extends Show[Orange] {
def show(orange: Orange) = "Standard orange"
}
}
def getAsString[T](target: T)(implicit s: Show[T]) = s show target
I also have list of fruits that I would like to show to the user using Show (this is my main goal in this question):
val basket = List[Fruit](Apple(), Orange())
def printList[T](list: List[T])(implicit s: Show[T]) =
list foreach (f => println(s show f))
printList(basket)
This will not compile because List is parametrized with Fruit and I have not defined any Show[Fruit]. What is the best way to achieve my goal using type classes?
I tried to find solution for this problem, but unfortunately have not found any nice one yet. It's not enough to know s in printList function - somehow it needs to know Show[T] for each element of the list. This means, that in order to be able to make this, we need some run-time mechanism in addition to the compile-time one. This gave me an idea of some kind of run-time dictionary, that knows, how to find correspondent Show[T] at run-time.
Implementation of implicit Show[Fruit]can serve as such dictionary:
implicit object FruitShow extends Show[Fruit] {
def show(f: Fruit) = f match {
case a: Apple => getAsString(a)
case o: Orange => getAsString(o)
}
}
And actually very similar approach can be found in haskell. As an example, we can look at Eq implementation for Maybe:
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
The big problem with this solution, is that if I will add new subclass of Fruit like this:
case class Banana extends Fruit
object Banana {
implicit object BananaShow extends Show[Banana] {
def show(banana: Banana) = "New banana"
}
}
and will try to print my basket:
val basket = List[Fruit](Apple(), Orange(), Banana())
printList(basket)
then scala.MatchError would be thrown because my dictionary does not know anything about bananas yet. Of course, I can provide updated dictionary in some context that knows about bananas:
implicit object NewFruitShow extends Show[Fruit] {
def show(f: Fruit) = f match {
case b: Banana => getAsString(b)
case otherFruit => Show.FruitShow.show(otherFruit)
}
}
But this solution is far from perfect. Just imagine that some other library provides another fruit with it's own version of dictionary. It will just conflict with NewFruitShow if I try to use them together.
Maybe I'm missing something obvious?
Update
As #Eric noticed, there is one more solution described here: forall in Scala . It's really looks very interesting. But I see one problem with this solution.
If I use ShowBox, then it will remember concrete type class during it's creation time. So I generally building list with objects and correspondent type classes (so dictionary in present in the list). From the other hand, scala has very nice feature: I can drop new implicits in the current scope and they will override defaults. So I can define alternative string representation for the classes like:
object CompactShow {
implicit object AppleCompactShow extends Show[Apple] {
def show(apple: Apple) = "SA"
}
implicit object OrangeCompactShow extends Show[Orange] {
def show(orange: Orange) = "SO"
}
}
and then just import it in current scope with import CompactShow._. In this case AppleCompactShow and OrangeCompactShow object would be implicitly used instead of defaults defined in the companion object of Show. And as you can guess, list creation and printing happens in different places. If I will use ShowBox, than most probably I will capture default instances of type class. I would like to capture them at the last possible moment - the moment when I call printList, because I even don't know, whether my List[Fruit] will ever be shown or how it would be shown, in the code that creates it.
The most obvious answer is to use a sealed trait Fruit and a Show[Fruit]. That way your pattern matches will complain at compile time when the match is not exhaustive. Of course, adding a new kind of Fruit in an external library will not be possible, but this is inherent in the nature of things. This is the "expression problem".
You could also stick the Show instance on the Fruit trait:
trait Fruit { self =>
def show: Show[self.type]
}
case class Apple() extends Fruit { self =>
def show: Show[self.type] = showA
}
Or, you know, stop subtyping and use type classes instead.