How to tell to the compiler a function can only return subtype of a trait and not the trait itself?

Viewed 83

I am trying to write some code that takes an URL, get the content from this URL, and saves it. The method to get the content from an URL is specific to the website, but then the content extracted uses a common format and can be saved in a database for example.

So I first define a type representing each supported URL:

trait Url(url: String)

case class Soundcloud(url: String) extends Url(url)
case class Youtube(url: String) extends Url(url)

Then a method that given a string return the correct URL type:

def matchUrl(s: String): Option[Url] =
  val s_regex = "soundcloud.com".r
  var y_regex = "youtube.com".r
  s match
    case s_regex() => Some(Soundcloud(s))
    case y_regex() => Some(Youtube(s))
    case _         => None

Then I define a trait for the content extractors:

trait GetContent[T <: Url]:
  def get: Unit  // should actually return something like `Content`

Here is my method to save an URL's content in the db:

def save[T <: Url](t: T)(using gc: GetContent[T]) =
  gc.get
  // save into db

Then I provide two implementations of GetContent, one for each URL type:

given GetContent[Soundcloud] with
  def get = println("getting some content from soundcloud")

given GetContent[Youtube] with
  def get = println("getting some content from youtube")

And finally I can plug it all together:

matchUrl("youtube.com").map(r => save(r))

The compiler complains that

no implicit argument of type GetContent[Url] was found for parameter gc of method save

If I call my method like this it works fine:

save(Soundcloud("http://soundcloud.com/some/content"))

I need to tell the compiler that matchUrl can only return an instance of Soundcloud or Youtube, never a Url.

1 Answers

One way to solve your problems is leveraging transparent inline. Furthermore, if you want to tell that the method will eventually return a Soundcloud instance or a Youtube instance, you can use the union type |.

In this case, the compiler refines the return type with the subtype return:


transparent inline def matchUrl(s: String): Option[Soundcloud | Youtube] =
  inline s match
    // I use some regexs here for the real implementation
    case "soundcloud.com" => Some(Soundcloud(s))
    case "youtube.com"    => Some(Youtube(s))
    case _                => None

In this way, calling matchUrl("soundcloud.com") will return the refined type Option[Soundcloud] and then the compiler can find the implicit accordingly.

Scastie snippet

Related