Search code examples
scalatype-safetyalgebraic-data-types

Type-safety with ADT and Aux pattern


I'm designing type-safe code with ADT and Aux-pattern and cannot get rid of some asInstanceOf. Here is the example:

  sealed trait Source
  case object FileSystem extends Source
  case object Network extends Source

  sealed trait Data {
    type S <: Source
  }
  object Data {
    type Aux[T <: Source] = Data { type S = T }
  }
  case class RegularFile(path: String) extends Data { type S = FileSystem.type }
  case class Directory(path: String) extends Data { type S = FileSystem.type }
  case class UnixDevice(path: String) extends Data { type S = FileSystem.type }
  case class Remote(ip: String) extends Data { type S = Network.type }

  //Lots of asInstanceOf
  def availableData[S <: Source](src: Source): List[Data.Aux[S]] = {
    src match {
      case FileSystem => List(
        RegularFile("/tmp/test").asInstanceOf[Data.Aux[S]],
        Directory("/home/somename").asInstanceOf[Data.Aux[S]],
        UnixDevice("/dev/null").asInstanceOf[Data.Aux[S]],
      )
      case Network => List(
        Remote("192.168.0.1").asInstanceOf[Data.Aux[S]]
      )
    }
  }

In this case it is pretty obvious that asInstanceOf is correct, but is there a way to get rig of it?

I was considering S <: Source: ClassTag, but it doesn't seem to be useful here. Maybe other reflection trick?


Solution

  • Please see explanation why the signature

    def availableData[S <: Source](src: S): List[Data.Aux[S]] = {
      src match {
        case FileSystem => List(
          RegularFile("/tmp/test"),
          Directory("/home/somename"),
          UnixDevice("/dev/null"),
        )
        case Network => List(
          Remote("192.168.0.1")
        )
      }
    }
    

    doesn't work and several approachs how to fix it:

    Generic function where the return type depends on the input type in Scala?

    Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?

    Type mismatch on abstract type used in pattern matching

    For example try a type class

    trait AvailableData[S <: Source] {
      def availableData(src: S): List[Data.Aux[S]]
    }
    object AvailableData {
      implicit val fileSystem: AvailableData[FileSystem.type] = _ =>
        List[Data.Aux[FileSystem.type]](
          RegularFile("/tmp/test"),
          Directory("/home/somename"),
          UnixDevice("/dev/null"),
        )
      implicit val network: AvailableData[Network.type] = _ =>
        List[Data.Aux[Network.type]](
          Remote("192.168.0.1")
        )
    }
    
    def availableData[S <: Source](src: S)(implicit 
      ad: AvailableData[S]
    ): List[Data.Aux[S]] = ad.availableData(src)