Working more on FP in Scala examples, I tried to implement the Option trait's map function as follows:
sealed trait MyOption[+A] {
def map[B](f: A => B): Option[B] = this match {
case Some(a) => Some(f(a))
case _ => None
}
}
However, compile-time errors show, if I understand correctly, that I'm not pattern-matching correctly for the case of Some(A). Using pattern matching, how can I write the first case to get Some(A) values to match?
>scalac MyOption.scala
MyOption.scala:3: error: constructor cannot be instantiated to expected type;
found : Some[A(in class Some)]
required: MyOption[A(in trait MyOption)]
case Some(a) => Some(f(a))
^
MyOption.scala:3: error: not found: value a
case Some(a) => Some(f(a))
^
two errors found
You are trying to define map in terms of Some and None which are subclasses of the Scala-provided Option trait, rather than in terms of subclasses of your own trait. Try something like:
sealed trait MyOption[+A] {
import MyOption._
def map[B](f: A => B): MyOption[B] = this match {
case MySome(a) => MySome(f(a))
case _ => MyNone
}
}
object MyOption {
case class MySome[+A](a: A) extends MyOption[A]
case object MyNone extends MyOption[Nothing]
}
Related
I have a simple GADT declared like this:
sealed trait T[A]
object T {
case class MkT[A <: String with Singleton](name: A) extends T[A]
}
Now I would like to write a method that will check if the singleton type parameter is the same for two T objects and return evidence of that fact in the form of a cats.evidence.Is object if that is the case.
I've tried the following, but it doesn't work:
import cats.evidence.Is
def checkIs[A, B](ta: T[A], tb: T[B]): Option[Is[A, B]] =
(ta, tb) match {
case (ta: T.MkT[a], tb: T.MkT[b]) if ta.name == tb.name =>
Some(Is.refl[A])
case _ => None
}
// [error] Main.scala:36:75: type mismatch;
// [error] found : cats.evidence.Is[A,A]
// [error] required: cats.evidence.Is[A,B]
How can I convince the compiler that this is sound?
// edit: as #Dmytro Mitin pointed out, it seems paradoxical to do a run-time check and yet convince the compiler at compile-time that the types are the same. But this is in fact possible, and it can be demonstrated with a simpler GADT:
sealed trait SI[A]
object SI {
case object S extends SI[String]
case object I extends SI[Int]
}
def checkInt[A](si: SI[A]): Option[Is[A, Int]] =
si match {
case SI.I => Some(Is.refl[Int])
case _ => None
}
With the pattern matching you try to check that "the singleton type parameter is the same for two T objects" at runtime (ta.name == tb.name) but want to convince the compiler at compile time. I would try a type class
trait CheckIs[A, B] {
def checkIs(ta: T[A], tb: T[B]): Option[Is[A, B]]
}
object CheckIs {
implicit def same[A]: CheckIs[A, A] = (_, _) => Some(Is.refl[A])
implicit def diff[A, B]: CheckIs[A, B] = (_, _) => None
}
def checkIs[A, B](ta: T[A], tb: T[B])(implicit ci: CheckIs[A, B]): Option[Is[A, B]] = ci.checkIs(ta, tb)
checkIs(T.MkT("a"), T.MkT("a")) //Some(cats.evidence.Is$$anon$2#28f67ac7)
checkIs(T.MkT("a"), T.MkT("b")) //None
(By the way, Is is a type class, it's natural to use it as implicit constraint but a little weird to use it as return type.)
I have following ADT
sealed trait Option[+A] {
def map[B](f: A => B): Option[B] = this match {
case None => None
case Some(a) => Some(f(a))
}
def getOrElse[B>:A](default: => B): B = this match {
case None => default
case Some(a) => a
}
}
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]
I've play a bit with the getOrElse function:
scala> Some(4).getOrElse(44)
res1: Int = 4
scala> Some(4).getOrElse("Hello")
res2: Any = 4
Why the last return the Any type?
You've said B must be a super type of A.
In the first example, both A and B are ints, so there's no problem.
In the second example, you have String and ints. The only super type of int that satisfies your constraint [B>:A] is Any.
Think about it like this: what type did you expect it would return? You have an int type defaulting to a String type. The type must be Any for that to be possible.
sealed trait Option_40to49[+A] {
def map[B](f: A => B): Option[B] = this match {
case None => None
case Some(x) => Some(f(x))
}
}
I work in eclipse, it underlined None with the next error:
pattern type is incompatible with expected type; found : None.type required: packageName.Option_40to49[A]
and the similar with Some(x)
constructor cannot be instantiated to expected type; found : Some[A(in class Some)] required: packageName.Option_40to49[A(in trait Option_40to49)]
Why I have this problem? How to fix it?
By using this in your pattern match you're referring to your Option_40to49, but since you haven't implemented None or Some the compiler doesn't know what these are
Simple versions of these aren't that hard to implement yourself. Note, you'll also want to change your output for map to Option_40to49
sealed trait Option_40to49[+A] {
def map[B](f: A => B): Option_40to49[B] = this match {
case None => None
case Some(x) => Some(f(x))
}
}
case class Some[A](x: A) extends Option_40to49[A]
case object None extends Option_40to49[Nothing]
I am trying to write a scala function to reverse a list. However, IDEA is highlighting the (warning) line saying "fruitless type test: a value of type ListDefinition.List[T] cannot also be a::[B]"
object ListDefinition {
def reverseList[T](list: List[T]) : List[T] = list match {
(warning) case x :: EmptyList => // something
case _ => // do somehting
}
abstract class List[+T]
case object EmptyList extends List[Nothing]
case class ConsList[T](value: T, next: List[T]) extends List[T]
The case statement needs to use the Cons constructor for your list:
case ConsList(x, EmptyList) => // something
The :: constructor is for Scala lists and will not work for yours.
(this is based on the article at http://bertails.org/2015/02/15/abstract-algebraic-data-type)
First, I am defining an abstract version of scala.Option.
import scala.language.higherKinds
trait OptionSig {
type Option[+_]
type Some[+A] <: Option[A]
type None <: Option[Nothing]
}
abstract class OptionOps[Sig <: OptionSig] extends Extractors[Sig] {
def some[A](x: A): Sig#Some[A]
def none: Sig#None
def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B
}
I want to be able to use pattern matching on Sig#Option[A] so Extractors looks like that:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](opt: Sig#Option[A]): scala.Option[A] =
fold(opt)(scala.None, a => scala.Some(a))
}
object None {
def unapply[A](opt: Sig#Option[A]): Option[Unit] =
fold(opt)(scala.Some(()), _ => scala.None)
}
}
Now I can write this program:
class Program[Sig <: OptionSig](implicit ops: OptionOps[Sig]) extends App {
import ops._
val opt: Sig#Option[Int] = some(42)
opt match {
case None(_) => sys.error("")
case Some(42) => println("yay")
case Some(_) => sys.error("")
}
}
And I can test it with this implementation.
trait ScalaOption extends OptionSig {
type Option[+A] = scala.Option[A]
type Some[+A] = scala.Some[A]
type None = scala.None.type
}
object ScalaOption {
implicit object ops extends OptionOps[ScalaOption] {
def some[A](x: A): ScalaOption#Some[A] = scala.Some(x)
val none: ScalaOption#None = scala.None
def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B =
opt match {
case scala.None => ifNone
case scala.Some(x) => ifSome(x)
}
}
}
object Main extends Program[ScalaOption]
It looks like it works but there is one annoying thing I cannot figure out.
With, scala.Option, the type of s in Option(42) match { case s # Some(42) => s } is Some[Int]. But with my snippet above, it is Sig#Option[Int] and I would like to make it Sig#Some[Int] instead.
So I tried the following to be closer to what scalac generates for its case classes:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
fold(n)(scala.Some(()), (_: Any) => scala.None)
}
}
But now I get warnings like the following:
[warn] Main.scala:78: abstract type pattern Sig#None is unchecked since it is eliminated by erasure
[warn] case None(_) => sys.error("")
I am not sure why this is happening as Sig#None is a subtype of Sig#Option[Int] and this is known at compile time.
Also the runtime is still ok, but the inferred type is still not the one I was expecting.
So the questions are
why is type erasure mentioned here despite the subtyping information?
how to get Sig#Option[Int] for s in (some(42): Sig#Option[Int]) match { case s # Some(42) => s }
Unfortunately, you cannot do what you want. The problem is that it is not enough that scalac know that Sig#None <: Sig#Option[A], it must be able to verify that the value it is passing to unapply is, in fact, Sig#None. This works for scala.Option, because the compiler can generate an instanceof check to verify that a type is actually a Some or a None before passing it to the unapply method. If it fails the check, it skips that pattern (and unapply is never called).
In your case, since scalac only knows that opt is a Sig#Option[Int], it cannot do anything to guarantee that the value is actually a Some or None before passing it along to unapply.
So what does it do? It passes the value along anyways! What does this mean? Well, let's modify your extractors a bit:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
scala.Some(())
}
}
All we've done is stopped using fold in the None case. Since we know the argument must be a Sig#None, why even bother calling fold, right? I mean, we wouldn't expect a Sig#Some to be passed here, right?
When we run this example, you'll hit a RuntimeException, because our very first pattern match succeeds and calls ???. In the case of scala.Option, the pattern would fail because it's guarded by a generated instanceof check.
I've gisted another example that shows another danger, where we add a small constraint to Sig#Some, which let's us avoid the fold in the Some case too: https://gist.github.com/tixxit/ab99b741d3f5d2668b91
Anyways, your specific case is technically safe. We know that you used fold and so it is safe to use with an Sig#Option. The problem is that scalac doesn't know that.