Search code examples
scalatypesinlinetype-systemssingleton-type

Scala: is there a way to create inline types?


Basically, I'd like to be able to write something like this:

val x :('k1.type, Int) = 'k1 -> 1
val y :('k2.type, Int) = 'k2 -> 2

Where types of x and y are not compatible, but either share a super type or can be annotated with context bounds, allowing me to do something like this:

def mlt[T :MyLittleType](x :(T, Int)) = ???
mlt(x); mlt(y)

Keywords are used here only as an example, the goal is to be able to provide both literals and singleton types for some identifiers/keywords/strings. The types might as well get erased/unified in runtime, I am interested only in static type check. I guess it should be possible to attain this using macros, but I would rather not.


Solution

  • You can construct structural types inline:

    scala> val a = new {def hello = null} -> 1 // by the way hello is accessible in a (but scala uses reflection for that)
    a: (AnyRef{def hello: Null}, Int) = ($anon$1@68e2da47,1)
    
    scala> var b = new {def hello = null} -> 2
    b: (AnyRef{def hello: Null}, Int) = ($anon$1@77147ad6,2)
    
    scala> b = a
    b: (AnyRef{def hello: Null}, Int) = ($anon$1@68e2da47,1)
    
    scala> var c = new {def helloooo = null} -> 1
    c: (AnyRef{def helloooo: Null}, Int) = ($anon$1@38def4a2,1)
    
    scala> c = a
    <console>:15: error: type mismatch;
     found   : (AnyRef{def hello: Null}, Int)
     required: (AnyRef{def helloooo: Null}, Int)
           c = a
               ^
    

    So, you can combine them with objects to give them type uniqueness:

     new {def myTypeName = null} -> myObject //now your myObject tagged with 'myTypeName', but your methods should be aware about tuples
    
     def mlp(x: ((Any, YourObjectsType), Int)) = x
    

    Or (little bit slower beacause of reflection)

    scala> def mlp( x: ({def obj: Symbol}, Int)) = x._1.obj -> x._2
    warning: there were 1 feature warning(s); re-run with -feature for details
    mlp: (x: (AnyRef{def obj: Symbol}, Int))(Symbol, Int)
    
    scala> mlp(new { def a1 = null; def obj = 'a1 } -> 1)
    res18: (Symbol, Int) = ('a1,1)
    
    scala> mlp(new { def a2 = null; def obj = 'a2 } -> 1)
    res19: (Symbol, Int) = ('a2,1)
    

    You can combine it with tags to annotate your type like:

    import scalaz._
    import Scalaz._
    
    scala> def markALittle[T](a: T) = Tag[T, MyLittleType](a)
    markALittle: [T](a: T)scalaz.@@[T,MyLittleType]
    
    scala> markALittle(new {def hello: Aaa = null})
    res15: scalaz.@@[AnyRef{def hello: Aaa},MyLittleType] = $anon$1@a8c48e8
    

    More tagging examples:

    scala> trait MyLittleType
    
    scala> trait Spike extends MyLittleType; val x = Tag[Symbol, Spike]('k1) -> 1
    x: (scalaz.@@[Symbol,Spike], Int) = ('k1,1)
    
    scala> trait Rainbow extends MyLittleType; val y = Tag[Symbol, Rainbow]('k2) -> 1
    y: (scalaz.@@[Symbol,Rainbow], Int) = ('k2,1)
    
    scala> val y: (scalaz.@@[Symbol,Spike], Int) = Tag[Symbol, Rainbow]('k1) -> 1
    <console>:22: error: type mismatch;
     found   : (scalaz.@@[Symbol,Rainbow], Int)
     required: (scalaz.@@[Symbol,Spike], Int)
           val y: (scalaz.@@[Symbol,Spike], Int) = Tag[Symbol, Rainbow]('k1) -> 1
    
    
    scala> val z: (scalaz.@@[Symbol,_ <: MyLittleType], Int) = Tag[Symbol, Rainbow]('k1) -> 1
    z: (scalaz.@@[Symbol, _ <: MyLittleType], Int) = ('k1,1)
    

    So, you can:

    scala> def mlt[T <: MyLittleType](x :(scalaz.@@[Symbol,T], Int)) = x
    mlt: [T <: MyLittleType](x: (scalaz.@@[Symbol,T], Int))(scalaz.@@[Symbol,T], Int)
    
    scala> mlt(x)
    res42: (scalaz.@@[Symbol,Spike], Int) = ('k1,1)
    
    scala> mlt(y)
    res43: (scalaz.@@[Symbol,Rainbow], Int) = ('k2,1)
    

    Or just use:

    scala> val x = Tag[Int, Rainbow](1)
    x: scalaz.@@[Int,Rainbow] = 1
    
    scala> val y = Tag[Int, Spike](1)
    y: scalaz.@@[Int,Spike] = 1
    

    You may operate x as both Int using Tag.unwrap(x), or just define implicit def t[T] = Tag.unwrap[Int, T] _ to make no difference between Tag and Int, but be careful here - any non-tag oriented function will remove the tag)

    Another inline type constructor solutions:

    a) ugly

    scala> class ___
    defined class ___
    
    scala> class __[T,U] extends ___
    defined class __
    
    scala> val x = Tag[Symbol, ___ __ ___]('k1) -> 1
    x: (scalaz.@@[Symbol,__[___,___]], Int) = ('k1,1)
    
    scala> var y = Tag[Symbol, ___ __ ___ __ ___]('k1) -> 1
    y: (scalaz.@@[Symbol,__[__[___,___],___]], Int) = ('k1,1)
    
    scala> y = x
    <console>:59: error: type mismatch;
     found   : (scalaz.@@[Symbol,__[___,___]], Int)
     required: (scalaz.@@[Symbol,__[__[___,___],___]], Int)
           y = x
               ^
    
    scala> def mlp[X <: scalaz.@@[Symbol, _]](x: (X, Int)) = x
    mlp: [X <: scalaz.@@[Symbol, _]](x: (X, Int))(X, Int)
    
    scala> mlp(x)
    res106: (scalaz.@@[Symbol,__[___,___]], Int) = ('k1,1)
    

    b) funny:

    class - [B <: -[_, _], A <: symbolic[A]] (a: A, b: B) { 
        def -[T <: symbolic[T]](c: T) = new - (c, this)
    }
    
    trait symbolic[F <: symbolic[F]] { 
        def - [T <: symbolic[T]](b: T) = new - [single[F],T](b, new single(this.asInstanceOf[F]))
    }
    
    class single[T <: symbolic[T]](a: T) extends - [single[_],T](a, null)
    
    val a = new a_; class a_ extends symbolic[a_]
    val b = new b_; class b_ extends symbolic[b_]
    val c = new c_; class c_ extends symbolic[c_] 
    ...
    
    scala> val x = h-e-l-l-o -> 1
    x: (-[o_,-[l_,-[l_,-[h_,end[e_]]]]], Int) = ($minus@350bc88,1)
    
    scala> var y = h-e-l-l-o-o -> 2
    y: (-[o_,-[o_,-[l_,-[l_,-[h_,end[e_]]]]]], Int) = ($minus@46d7fdc0,2)
    
    scala> y = x
    <console>:13: error: type mismatch;
     found   : (-[o_,-[l_,-[l_,-[h_,end[e_]]]]], Int)
     required: (-[o_,-[o_,-[l_,-[l_,-[h_,end[e_]]]]]], Int)
           y = x
               ^
    
    scala> var z = h-e-l-l-o-o -> 2
    z: (-[o_,-[o_,-[l_,-[l_,-[h_,end[e_]]]]]], Int) = ($minus@6b899d5d,2)
    
    scala> z = y
    z: (-[o_,-[o_,-[l_,-[l_,-[h_,end[e_]]]]]], Int) = ($minus@46d7fdc0,2)