• Beginning Scala study note(8) Scala Type System


    1. Unified Type System
    Scala has a unified type system, enclosed by the type Any at the top of the hierarchy and the type Nothing at the bottom of the hierarchy. All Scala types inherit from Any.

    # Using Any, Book extends AnyRef, and x is an Int that extends AnyVal
    scala> import scala.collection.mutable.ListBuffer
    import scala.collection.mutable.ListBuffer
    scala> val list = ListBuffer[Any]()
    list: scala.collection.mutable.ListBuffer[Any] = ListBuffer()
    scala> val x = 2
    x: Int = 2
    scala> list += x
    res11: list.type = ListBuffer(2)
    scala> class Book
    defined class Book
    scala> list += new Book
    res12: list.type = ListBuffer(2, Book@4894a95e)
    
    # limit a method to only be able to work on Value Types
    scala> def test(int: AnyVal) = ()
    test: (int: AnyVal)Unit
    scala> test(5)
    scala> test(5.12)
    scala> test(new Object)
    <console>:10: error: type mismatch;
    found : java.lang.Object
    required: AnyVal
    test(new Object)
    ^

    2. Type Parameterization
      Classes and traits that take type parameters are called generic; the types they generate are called parameterized type.

    # Scala List of Strings
    scala> val list: List[String] = List("A","B")

       Note that the Set[T] is a trait, but not a type because it takes a type parameter.

    # Scala requires to specify type parameters
    scala> def test(s: Set){}
    <console>:8: error: type Set takes type parameters
    def test(s: Set){}
    ^
    # specifying parameter types
    scala> def test(s: Set[AnyRef]){}
    test: (s: Set[AnyRef])Unit

       In Scala, List, Set and so on also be referred as a type constructors, because they are used to create specific types. You could construct a type by specifying a type parameter.

        (1) Variance
        A declaration like class Set[+A] means that Set is parameterized by a type A. The + is called a variance annotation【型变注释】.
        Variance comes in three flavors: invariant【不变】, covariant【协变】, and contravariant【逆变】. Variance in Scala is defined by using + and - signs in front of type parameters.
          1) Covariant Parameter Types
          Covariant parameter types are designated with a + before the type parameter. A covariant type is useful for read-only containers.

           If S extends T then Class[S] extends Class[T].

    # immutable class Getable
    scala> class Getable[+T](val data: T)
    defined class Getable
    # define a method that takes a Getalbe
    scala> def get(in: Getable[Any]){println("It's "+in.data)}
    get: (in: Getable[Any])Unit
    # instance of Getable
    scala> val gs = new Getable("String")
    gs: Getable[java.lang.String] = Getable@6a6877c1
    scala> get(gs)
    It's String
    
    #passing Double
    scala> def getNum(in: Getable[Number]) = in.data.intValue
    getNum: (in: Getable[java.lang.Number])Int
    scala> def gd = new Getable(new java.lang.Double(3.3))
    gd: Getable[java.lang.Double]
    scala> getNum(gd)
    res17: Int = 3

           We can make read-only classes covariant.

        2) Contravariant Parameter Types
          Contravariance indicates if S extends T, then class[T] extends Class[S].

    # Putable Class
    scala> class Putable[-T]{def put(in: T){println("Putting "+in)}}
    defined class Putable
    # method takes a Putable[String]
    scala> def writeOnly(in: Putable[String]){in.put("Hello")}
    writeOnly: (in: Putable[String])Unit
    # declare an instance of Putable[AnyRef]
    scala> val p = new Putable[AnyRef]
    p: Putable[AnyRef] = Putable@3a5a8982
    scala> writeOnly(p)
    Putting Hello

          The inputs to a transformation are contravariant. Calling something that expects at least AnyRef with a String is legal and valid. But the return value can be covariant because we expect to get back a Number, so if we get an Integer, a subclass of Number, we're okay.

    # define DS with a contravariant In type and a covariant Out type.
    scala> trait DS[-In, +Out]{def apply(i: In): Out}
    defined trait DS
    # create an instance that will convert Any into an Int
    scala> val t1 = new DS[Any, Int]{def apply(i: Any) = i.toString.toInt}
    t1: java.lang.Object with DS[Any,Int] = $anon$1@4f7620f1
    scala> def check(in: DS[String, Any]) = in("333")
    check: (in: DS[String,Any])Any
    scala> check(t1)
    res19: Any = 333

         3) Invariant Parameter Types

          In Scala, Array[T] is invariant. This means that you can only pass an Array[String] to foo(a: Array[String]).

    # define an invariant class
    scala> class Holder[T](var data: T)
    defined class Holder
    scala> def add(in: Holder[Int]){in.data = in.data + 1}
    add: (in: Holder[Int])Unit
    scala> val h = new Holder(0)
    h: Holder[Int] = Holder@3d85fdbe
    scala> add(h)
    scala> h.data
    res21: Int = 1

         Because the add method expects an Int to come out of Holder and puts an Int back into the Holder, the type of the Holder must be invariant.

         Let's put a Double into a Holder[Number]:

    scala> val nh = new Holder[Number](33.3d)
    nh: Holder[java.lang.Number] = Holder@4e3540c7
    scala> def round(in: Holder[Number]){in.data = in.data.intValue}
    round: (in: Holder[java.lang.Number])Unit
    scala> round(nh)
    scala> nh.data
    res4: java.lang.Number = 33

         We put a Number and got back a Number.

    # the underlying class
    scala> nh.data.getClass
    res5: java.lang.Class[_ <: java.lang.Number] = class java.lang.Integer
    
    # try to pass a Holder[Double] into round
    scala> val dh = new Holder(33.3d)
    dh: Holder[Double] = Holder@5a3a5cc5
    
    scala> round(dh)
    <console>:11: error: type mismatch;
    found : Holder[Double]
    required: Holder[java.lang.Number]
    round(dh)
    ^

         4) Rules of Variance

        Mutable containers should be invariant. Immutable containers should be covariant. Inputs to transformations should be contravariant, and outputs from transformations should be covariant.
     
     (2) Type Bounds
        1) Upper Type Bounds
        An Upper bound type is restricted to a specific type or one of its derived types. Scala provides the upper bound relation operator (<:). The <: operator signifies that the type to the left of the <: operator must be a subtype or be the same type of the type to the right of the <: operator.

    #define an Employee class hierarchy
    scala> class Employee(val name: String)
    defined class Employee
    scala> class Internal(name: String) extends Employee(name)
    defined class Internal
    scala> class FreeLancer(name: String) extends Employee(name)
    defined class FreeLancer
    scala> class Customer(name: String)
    defined class Customer
    
    # define a function that takes a parameter with an upper bound
    scala> def employeeName[A <: Employee](emp: A){println(emp.name)}
    employeeName: [A <: Employee](emp: A)Unit
    
    scala> employeeName(new Internal("Paul"))
    Paul
    scala> employeeName(new FreeLancer("John"))
    John
    # the Customer class is not a subtype of Employee.
    scala> employeeName(new Customer("Peter"))
    <console>:11: error: inferred type arguments [Customer] do not conform to method employeeName's type parameter bounds [A <: Employee]
    employeeName(new Customer("Peter"))
    ^

       2) Lower Type Bounds
      A lower bound type is restricted to a specific type or its supertype. The type selected must be equal to or a supertype of the lower bound restriction.

    scala> class A{
    | type B >: List[Int]
    | def something(a: B) = a
    | }
    defined class A
    # instantiate the subtype
    scala> val st = new A{ type B = Traversable[Int]}
    st: A{type B = Traversable[Int]} = $anon$1@57a3687
    scala> st.someMethod(Set(1,2))
    res0: st.B = Set(1, 2)

       Even if Set is not a supertype of the List class, it's a subtype of Traversable.
    3. Implicit Class
      The String class seems to have grown methods:

    scala> "Hello".toList
    res0: List[Char] = List(H, e, l, l, o)

       If you have an instance of a particular type, and you need another type, and there's an implicit conversion in scope, Scala will call the implicit method to perform the conversion.

    # create a method that calculates the number of days based on a Long containing a millisecond count
    scala> def millisToDays(in: Long): Int = (in / (1000L*3600L*24)).toInt
    millisToDays: (in: Long)Int
    scala> millisToDays(5949440999L)
    res1: Int = 68
    # try to pass a Date into the method
    scala> millisToDays(new Date)
    <console>:14: error: type mismatch;
    found : java.util.Date
     required: Long
    millisToDays(new Date)
                     ^
    # define a method that will automatically be called when we need the conversion
    scala> implicit def dateToLong(d: Date) = d.getTime
    dateToLong: (d: java.util.Date)Long
    scala> millisToDays(new Date)
    res5: Int = 17048    

      You should be very careful with implicit conversions, and their use should be an explicit design choice. However, we see that sometimes implicit conversions(Int -> Long) are very valuable.

    # a method that takes a parameter that must be a Long
    scala> def m2[T <: Long](in: T):Int = (in / (1000L * 3600L * 24L)).toInt
    m2: [T <: Long](in: T)Int
    scala> m2(33)
    <console>:15: error: inferred type arguments [Int] do not conform to method m2's type parameter bounds [T <: Long]
     m2(33)
        ^
    scala> m2(33L)
    res7: Int = 0  

       What is the scope of implicits? Scala compiler considers an implicit in the current scope if:
      1) The implicit is defined in the current class or in a superclass.
      2) The implicit is defined in a trait or supertrait, or is mixed into the current class or superclass
      3) The implicit is defined on the companion object of the current target class.
      4) The implicit is available on an object that has been imported into the current scope.
      When designing libraries, be careful about defining implicits, and make sure they are in as narrow a scope as is reasonable. When consuming libraries, make sure the implicits defined in the objects are narrow enough and are not going to cause problems such a getting stuff from every Option.
      An implicit class is a class marked with the implicit keyword. The keyword makes the class's primary constructor available for implicit conversions when the class is in scope.

  • 相关阅读:
    【luogu P7418】Counting Graphs P(DP)(思维)(容斥)
    【luogu P7417】Minimizing Edges P(贪心)(思维)
    多边形序列(组合数)(高精)(NTT)
    【luogu P3803】【模板】多项式乘法(NTT)
    【luogu P1919】【模板】A*B Problem升级版(FFT快速傅里叶)
    【luogu P6139】【模板】广义后缀自动机(广义 SAM)
    【luogu P7529】Permutation G(几何)(数学)(DP)
    【luogu P5787】graph / 二分图 /【模板】线段树分治(扩展域并查集)(线段树分治)
    同桌的你(环套树)(DP)
    石子游戏(博弈论)(Spaly)
  • 原文地址:https://www.cnblogs.com/mengrennwpu/p/6201163.html
Copyright © 2020-2023  润新知