Search code examples
scalaalgebraic-data-typesgadt

What is the purpose of using type parameters for an empty trait when extending it?


I have recently come across a code that looks similar to the following.

The question I want to ask is what would be the possible purposes of the Second's type parameters(namely the first type parameter) specified when being extended by the case classes.

I do not see any particular reason to use A, Option[A] and Seq[A] as the type parameters are narrowed down by the extending case classes by [A <: SomeTrait, B <: AnotherTrait].

Am I missing any important points?

sealed trait Top[A, B]

sealed trait Second[A, B] extends Top[A, B]

case class ThirdA[A <: SomeTrait, B <: AnotherTrait](
  returnType: Class[A], 
  relation: B
) extends Second[A, B]

case class ThirdB[A <: SomeTrait, B <: AnotherTrait](
  returnType: Class[A],
  relation: B
) extends Second[Option[A], B]

case class ThirdC[A <: SomeTrait, B <: AnotherTrait](
  returnType: Class[A],
  relation: B
) extends Second[Seq[A], B]

Solution

  • Following your logic

    I mean what would the type parameters A, Option[A] and Seq[A] be trying to achieve(enforce) here? My understanding is that using Only A is the same here as long as the Second is empty.

    this standard GADT

    //  data Lam :: * -> * where
    //    Lift :: a                     -> Lam a        -- ^ lifted value
    //    Pair :: Lam a -> Lam b        -> Lam (a, b)   -- ^ product
    //    Lam  :: (Lam a -> Lam b)      -> Lam (a -> b) -- ^ lambda abstraction
    //    App  :: Lam (a -> b) -> Lam a -> Lam b        -- ^ function application
    //    Fix  :: Lam (a -> a)          -> Lam a        -- ^ fixed point
    
      sealed trait Lam[A]
      case class Lift[A](a: A) extends Lam[A]
      case class Pair[A, B](la: Lam[A], lb: Lam[B]) extends Lam[(A, B)]
      case class LamC[A, B](f: Lam[A] => Lam[B]) extends Lam[A => B]
      case class App[A, B](f: Lam[A => B], la: Lam[A]) extends Lam[B]
      case class Fix[A](f: Lam[A => A]) extends Lam[A]
    

    doesn't make sense because Lift[A] can be obtained as new Lam[A] {}, Pair[A, B] as new Lam[(A, B)] {}, LamC[A, B] as new Lam[A => B], App[A, B] as new Lam[B] {}, Fix[A] as new Lam[A] {}, and moreover App[X, A] is the same as Fix[A] :)

    Well, yes, but objects of those types can be obtained having different objects firstly. For Lam[A] you need an A, for Pair[A, B] you need a Lam[A] and Lam[B] etc.

    Similarly, yes, ThirdB[A, B] is ThirdA[Option[A], B] and ThirdC is ThirdA[Seq[A], B]. But to have objects of those types you need to have different objects firstly. In order to have ThirdA[A, B] you need objects of types Class[A] and B, in order to have ThirdB[A, B] through ThirdA[Option[A], B] you need objects of types Class[Option[A]] and B but you can have ThirdB[A, B] directly just via Class[A] and B, in order to have ThirdC[A, B] through ThirdA[Seq[A], B] you need objects of types Class[Seq[A]] and B but you can have ThirdC[A, B] directly just via Class[A] and B.

    For example you can match on ThirdA, ThirdB, ThirdC and write some logic depending on type. So you can specify your general logic for Option and Seq.

    So actually this depends on what SomeTrait, AnotherTrait, Class are.