Scala導入ティップス

Scalaのアクターモデルライブラリ (Akka) を利用して、IoTデバイスによる分散型データ処理やテレビ会議システムなどに必要とされる、多くのタスクを効率良くリアルタイム処理するアプリケーションを開発するためのメモ。

Scalaについて(Stack Overflow)

https://docs.scala-lang.org/tour/basics.html

Scala Programming Language

Scala Standard Library

MVNReposiroty
https://mvnrepository.com/search?q=scala

Scala School
http://twitter.github.io/scala_school/

ScalaによるJavaアプリビルトツールsbt(Simple Build Tool)

sbtリファレンス
https://www.scala-sbt.org/1.x/docs/Getting-Started.html

アクターモデル

チートシート

ScalaによるプログラミングはJDK, Sbt, Akkaツールキットなどの開発環境を用意した上で以下edXのコースで学べます。
https://courses.edx.org/courses/course-v1:EPFLx+scala-reactiveX+3T2019/course/

要点が纏まっているので、導入時や復習時にチェックして下さい。

SDKMANによる開発環境インストール(OpenJDK, sbt)

SDKMANによりJavaアプリ開発ツールを一元管理

SDKMANのインストール

ホームディレクトリにて以下bashスクリプトを実行してインストール

$ curl -s "https://get.sdkman.io" | bash

初期スクリプト実行

$ source "$HOME/.sdkman/bin/sdkman-init.sh"

バージョン確認

$ sdk version

SDKMAN 5.9.0+555

Java開発ツールリストアップ

$ sdk list java
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 15.0.0.j9    | adpt    |            | 15.0.0.j9-adpt      
               |     | 15.0.0.hs    | adpt    |            | 15.0.0.hs-adpt      
               |     | 14.0.2.j9    | adpt    |            | 14.0.2.j9-adpt      
               |     | 14.0.2.hs    | adpt    |            | 14.0.2.hs-adpt      
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt      
               |     | 13.0.2.hs    | adpt    |            | 13.0.2.hs-adpt      
               |     | 12.0.2.j9    | adpt    |            | 12.0.2.j9-adpt      
               |     | 12.0.2.hs    | adpt    |            | 12.0.2.hs-adpt      
               |     | 11.0.8.j9    | adpt    |            | 11.0.8.j9-adpt      
               | >>> | 11.0.8.hs    | adpt    | installed  | 11.0.8.hs-adpt      
               |     | 8.0.265.j9   | adpt    |            | 8.0.265.j9-adpt     
               |     | 8.0.265.hs   | adpt    |            | 8.0.265.hs-adpt     
 Amazon        |     | 15.0.0       | amzn    |            | 15.0.0-amzn         
               |     | 11.0.8       | amzn    |            | 11.0.8-amzn         
               |     | 8.0.265      | amzn    |            | 8.0.265-amzn        
 Azul Zulu     |     | 15.0.0       | zulu    |            | 15.0.0-zulu         
               |     | 15.0.0.fx    | zulu    |            | 15.0.0.fx-zulu      
               |     | 14.0.2       | zulu    |            | 14.0.2-zulu         
               |     | 13.0.4       | zulu    |            | 13.0.4-zulu         
               |     | 13.0.4.fx    | zulu    |            | 13.0.4.fx-zulu      
               |     | 12.0.2       | zulu    |            | 12.0.2-zulu         
               |     | 11.0.8       | zulu    |            | 11.0.8-zulu         
               |     | 11.0.8.fx    | zulu    |            | 11.0.8.fx-zulu      
               |     | 10.0.2       | zulu    |            | 10.0.2-zulu         
               |     | 9.0.7        | zulu    |            | 9.0.7-zulu          
               |     | 8.0.265      | zulu    |            | 8.0.265-zulu        
               |     | 8.0.265.fx   | zulu    |            | 8.0.265.fx-zulu     
               |     | 8.0.232.fx   | zulu    |            | 8.0.232.fx-zulu     
               |     | 7.0.262      | zulu    |            | 7.0.262-zulu        
               |     | 6.0.119      | zulu    |            | 6.0.119-zulu        
 BellSoft      |     | 15.0.0.fx    | librca  |            | 15.0.0.fx-librca    
               |     | 15.0.0       | librca  |            | 15.0.0-librca       
               |     | 14.0.2.fx    | librca  |            | 14.0.2.fx-librca    
               |     | 14.0.2       | librca  |            | 14.0.2-librca       
               |     | 13.0.2.fx    | librca  |            | 13.0.2.fx-librca    
               |     | 13.0.2       | librca  |            | 13.0.2-librca       
               |     | 12.0.2       | librca  |            | 12.0.2-librca       
               |     | 11.0.8.fx    | librca  |            | 11.0.8.fx-librca    
               |     | 11.0.8       | librca  |            | 11.0.8-librca       
               |     | 8.0.265.fx   | librca  |            | 8.0.265.fx-librca   
               |     | 8.0.265      | librca  |            | 8.0.265-librca      
 GraalVM       |     | 20.2.0.r11   | grl     |            | 20.2.0.r11-grl      
               |     | 20.2.0.r8    | grl     |            | 20.2.0.r8-grl       
               |     | 20.1.0.r11   | grl     |            | 20.1.0.r11-grl      
               |     | 20.1.0.r8    | grl     |            | 20.1.0.r8-grl       
               |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl      
               |     | 20.0.0.r8    | grl     |            | 20.0.0.r8-grl       
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl      
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl       
 Java.net      |     | 16.ea.18     | open    |            | 16.ea.18-open       
               |     | 16.ea.6.lm   | open    |            | 16.ea.6.lm-open     
               |     | 15           | open    |            | 15-open             
               |     | 14.0.2       | open    |            | 14.0.2-open         
               |     | 13.0.2       | open    |            | 13.0.2-open         
               |     | 12.0.2       | open    |            | 12.0.2-open         
               |     | 11.0.8       | open    |            | 11.0.8-open         
               |     | 10.0.2       | open    |            | 10.0.2-open         
               |     | 9.0.4        | open    |            | 9.0.4-open          
               |     | 8.0.265      | open    |            | 8.0.265-open        
 SAP           |     | 15.0.0       | sapmchn |            | 15.0.0-sapmchn      
               |     | 14.0.2       | sapmchn |            | 14.0.2-sapmchn      
               |     | 13.0.2       | sapmchn |            | 13.0.2-sapmchn      
               |     | 12.0.2       | sapmchn |            | 12.0.2-sapmchn      
               |     | 11.0.8       | sapmchn |            | 11.0.8-sapmchn      
================================================================================
Use the Identifier for installation:

    $ sdk install java 11.0.3.hs-adpt

リストからOpenJDK:バージョン11.0.8.hs-adptをインストール

$ sdk install java 11.0.8.hs-adpt

ScalaによるJavaアプリビルドツールsbtのインストール

$ sdk install sbt

マルチバージョンのインストールが可能。以下ヘルプ画面を参照して使用したいバージョンを選択すること(default選択コマンド)。

$ sdk help

We periodically need to update the local cache. Please run:

  $ sdk update


Usage: sdk <command> [candidate] [version]
       sdk offline <enable|disable>

   commands:
       install   or i    <candidate> [version] [local-path]
       uninstall or rm   <candidate> <version>
       list      or ls   [candidate]
       use       or u    <candidate> <version>
       default   or d    <candidate> [version]
       home      or h    <candidate> <version>
       env       or e    [init]
       current   or c    [candidate]
       upgrade   or ug   [candidate]
       version   or v
       broadcast or b
       help
       offline           [enable|disable]
       selfupdate        [force]
       update
       flush             [archives|tmp|broadcast|version]

   candidate  :  the SDK to install: groovy, scala, grails, gradle, kotlin, etc.
                 use list command for comprehensive list of candidates
                 eg: $ sdk list
   version    :  where optional, defaults to latest stable if not provided
                 eg: $ sdk install groovy
   local-path :  optional path to an existing local installation
                 eg: $ sdk install groovy 2.4.13-local /opt/groovy-2.4.13

IDE IntelliJ

Currying

https://alvinalexander.com/scala/fp-book/partially-applied-functions-currying-in-scala/

scala> def add(x: Int, y: Int) = x + y
add: (x: Int, y: Int)Int

scala> (add _).isInstanceOf[Function2[_, _, _]]
res0: Boolean = true

scala> val addCurried = (add _).curried
addCurried: Int => (Int => Int) = <function1>

scala> addCurried(1)(2)
res1: Int = 3

scala> val addCurriedTwo = addCurried(2)
addCurriedTwo: Int => Int = <function1>

scala> addCurriedTwo(10)
res2: Int = 12

Call-by-Name: => Type

Function Types

The type A => B is the type of a function that takes an argument of type A and returns a result of type B.

So, Int => Int is the type of functions that map integers to integers.

Anonymous Function Syntax

Example of a function that raises its argument to a cube:

  (x: Int) => x * x * x

Here, (x: Int) is the parameter of the function, and x * x * x is its body.

The type of the parameter can be omitted if it can be inferred by the compiler from the context.

If there are several parameters, they are separated by commas:

  (x: Int, y: Int) => x + y

FunctionとMethod

Function val, =>

val add = (x: Int, y: Int) => x + y
println(add(1, 2)) // 3

Method =, def

def add(x: Int, y: Int): Int = x + y
println(add(1, 2)) // 3

Traits

https://www.artima.com/pins1ed/traits.html#12.7

Type Unit, Nothing

Ex) foreach による例

以下numbersに対して

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

Nothing 戻り値を返さない。演算結果の値も持たない。

scala> numbers.foreach((i: Int) => i * 2)

Unit 戻り値を返さないがdoubledという値を持つ

scala> val doubled = numbers.foreach((i: Int) => i * 2)
doubled: Unit = ()

Implicit による自動変換とパラメータ定義

https://www.artima.com/pins1ed/implicit-conversions-and-parameters.html

Implicit Function

Implicitによる自動変換
def strToInt(x: String) = x.toIntimplicitを前置きすることで、文字列として入力した数値を実数に自動変換する。

scala> implicit def strToInt(x: String) = x.toInt
strToInt: (x: String)Int

scala> "123"
res0: java.lang.String = 123

scala> val y: Int = "123"
y: Int = 123

scala> math.max("123", 111)
res1: Int = 123

Future, Promise

Future

import ExecutionContext.Implicits.global
val s = "Hello"
val f: Future[String] = Future {
  s + " future!"
}
f foreach {
  msg => println(msg)
}

Promise

ドキュメント
https://docs.scala-lang.org/overviews/core/futures.html

Future Example

取り敢えず処理を保留(定義)しておいて、結果が出次第、その後の処理を決定する。

import scala.concurrent.ExecutionContext.Implicits.global                  
import scala.concurrent.Future
def someTimeConsumingComputation(): Int = { 25 + 50 }
val theFuture = Future { someTimeConsumingComputation() }

theFuture.onComplete {
  case Success(result) => println(result)
  case Failure(t) => println(s"Error: ${t.getMessage}")
}

Accessor(get), Mutator(set)

インスタンスパラメータへの読取りと書込み

https://docs.scala-lang.org/style/naming-conventions.html#accessorsmutators

Javaの場合

public class Company {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Scalaの場合

class Company {
  private var _name: String = _

  def name = _name

  def name_=(name: String) {
    _name = name
  }
}

What’s the difference between :: and :::

scala> val list1 = List(1,2)
list1: List[Int] = List(1, 2)

scala> val list2 = List(3,4)
list2: List[Int] = List(3, 4)

scala> list1::list2
res0: List[Any] = List(List(1, 2), 3, 4)

scala> list1:::list2
res1: List[Int] = List(1, 2, 3, 4)

:: (cons) example

object Demo {
   def main(args: Array[String]) {
      val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
      val nums = Nil

      println( "Head of fruit : " + fruit.head )
      println( "Tail of fruit : " + fruit.tail )
      println( "Check if fruit is empty : " + fruit.isEmpty )
      println( "Check if nums is empty : " + nums.isEmpty )
   }
}

Output
applesヘッドNilテイルで、ヘッドを含まないリストを表す。

Head of fruit : apples
Tail of fruit : List(oranges, pears)
Check if fruit is empty : false
Check if nums is empty : true

List(1,2,3)とval list = 1 :: 2 :: 3 :: Nilは同義

scala> val list = 1 :: 2 :: 3 :: Nil
list: List[Int] = List(1, 2, 3)

@tailrec アノテーション

再帰的な関数において、直前の戻り値を呼び出して代入する方法は非効率となるため、コンパイル時、 Tail Recursion(末尾再帰) でない場合にエラー警告を出します。以下Tail Recursionである最大公約数 gcd 演算と、そうではない階乗 factorial 演算の例。

最大公約数 gcd

def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)

結果が出力されるまで、関数のパラメータのみが更新される。

gcd(14, 21) is evaluated as follows:

  gcd(14, 21)
  if (21 == 0) 14 else gcd(21, 14 % 21)
  if (false) 14 else gcd(21, 14 % 21)
  gcd(21, 14 % 21)
  gcd(21, 14)
  if (14 == 0) 21 else gcd(14, 21 % 14)
  if (false) 21 else gcd(14, 21 % 14)
  gcd(14, 7)
  gcd(7, 14 % 7)
  gcd(7, 0)
  if (0 == 0) 7 else gcd(0, 7 % 0)
  if (true) 7 else gcd(0, 7 % 0)
  7

階乗 factorial

def factorial(n: Int): Int =
    if (n == 0) 1 else n * factorial(n - 1)

関数の中で関数を呼び出すので、その都度戻り値がストックされる。

factorial(4) is evaluated as follows:
    
factorial(4)
      if (4 == 0) 1 else 4 * factorial(4 - 1)
      4 * factorial(3)
      4 * (3 * factorial(2))
      4 * (3 * (2 * factorial(1)))
      4 * (3 * (2 * (1 * factorial(0)))
      4 * (3 * (2 * (1 * 1)))
      24

上記 階乗 fractorialTail Recursionにする場合(@tainrecアノテーションでTail Recursionかどうかの判断)

import scala.annotation.tailrec

object TailRecursion{

  def factorial(n: Int): Int = {
    @tailrec
    def iter(x: Int, result: Int): Int =
      if (x == 0) result
      else iter(x - 1, result * x)

    iter(n, 1)
  }


  def main(args: Array[String]): Unit = {
    println(factorial(5))
  }
}

Case Classes

Case Class の場合、インスタンスとそのパラメーターはデフォルトで val と定義され、書換えることが出来ません。
Case Class では自動的にapplyメソッドが適用されるため、インスタンス作成の際は、 “new” を付加する必要がありません。

scala> case class Person(name: String, relation: String)
defined class Person

// "new" not needed before Person
scala> val christina = Person("Christina", "niece")
christina: Person = Person(Christina,niece)

その他Case Classの特徴については以下参照。

Case Classのapplyメソッドの適用例

case class Person(name: String, age: Integer)

以下のapplyメソッドが自動的に適用されます。

object Person {
def apply(name: String, age: Integer): Person = new Person(name,age)
}

以下、全て同義です。

val p0 = new Person("Frank", 23) // normal constructor

val p1 = Person("Frank", 23) // this uses apply

val p2 = Person.apply("Frank", 23) // using apply manually

上記p1は、コンストラクタではなく、applyメソッドを呼び出すメソッドとなります。

Sealed Class, Sealed Trait, Final Class

Sealedすることにより、その派生クラスは同一ファイル(コンパイル単位)内のみで定義される。

https://docs.scala-lang.org/tour/pattern-matching.html#sealed-classes

Final Class と Sealed Class の違い

Final Classextendによるクラスの拡張が出来ない。

Types

https://docs.scala-lang.org/tour/unified-types.html

Type hierarchy


List Members(Class)

型(Type)が混合したリスト(List)例

val list: List[Any] = List(
  "a string",
  732,  // an integer
  'c',  // a character
  true, // a boolean value
  () => "an anonymous function returning a string"
)

list.foreach(element => println(element))

出力

a string
732
c
true
<function>

構成要素に型Tを指定したList: List[T]
例)

  val fruit: List[String]    = List("apples", "oranges", "pears")
  val nums : List[Int]       = List(1, 2, 3, 4)
  val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
  val empty: List[Nothing]   = List()

Constructors of Lists

Actually, all lists are constructed from:

the empty list Nil, and
the construction operation :: (pronounced cons): x :: xs gives a new list with the first element x, followed by the elements of xs (which is a list itself).

For example:

  val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
  val nums  = 1 :: (2 :: (3 :: (4 :: Nil)))
  val empty = Nil

Nil == List.empty[A] == List()

scala> Nil
res15: scala.collection.immutable.Nil.type = List()

scala> List.empty[Int]
res16: List[Int] = List()

scala> List()
res17: List[Nothing] = List()

Manipulating Lists

It is possible to decompose lists with pattern matching:

Nil: the Nil constant,
p :: ps: A pattern that matches a list with a head matching p and a tail matching ps.

      nums match {
        // Lists of `Int` that starts with `1` and then `2`
        case 1 :: 2 :: xs => …
        // Lists of length 1
        case x :: Nil => …
        // Same as `x :: Nil`
        case List(x) => …
        // The empty list, same as `Nil`
        case List() =>
        // A list that contains as only element another list that starts with `2`
        case List(2 :: xs) => …
      }

例)リストへのインサート

object StandardLibrary{
  val cond: (Int, Int) => Boolean = (_: Int) < (_: Int)/*insert the correct condition for evaluating if the list is sorted*/
  def insert(x: Int, xs: List[Int]): List[Int] =
    xs match {
      case List() => x :: List.empty[Int]// TODO
      case y :: ys =>
        if (cond(x, y)) x :: y :: ys
        else y :: insert(x, ys)
    }

  def main(args: Array[String]): Unit = {
    println(insert(4, 1::8::Nil))
  }
}

Output
> List(1, 4, 8)

Option

インスタンスである Some[A]None を返す。
例)

import org.scalatest.{FlatSpec, Matchers}

object StandardLibrary extends FlatSpec with Matchers {

  def optionMap(x: Option[Int]) : Option[Int]= {
    x.map(x => x + 1)
  }

  def optionFilter(x: Option[Int]): Option[Int] = {
    x.filter(x => x % 2 == 0)
  }

  def optionFlatMap(x: Option[Int]): Option[Int] = {
    x.flatMap(x => Some(x + 1))
  }

  def main(args: Array[String]): Unit = {
    println(optionMap(Some(7)))
    println(optionMap(None))
    println(optionFilter(Some(5)))
    println(optionFilter(Some(6)))
    println(optionFlatMap(Some(4)))
    println(optionFlatMap(None))
  }

}

出力

Some(8)
None
None
Some(6)
Some(5)
None

Either

object StandardLibrary{

  def triple(x: Int): Int = 3 * x

  def tripleEither(x: Either[String, Int]): Either[String, Int] =
    x.map(triple) 

  def main(args: Array[String]): Unit = {
    println(tripleEither(Right(1)))
    println(tripleEither(Left("not a number")))
  }
}

Note)
x.right.map(triple) でも良いが、rightmapで統一

出力

Right(3)
Left(not a number)

Expansion of Function Values

An anonymous function such as

  (x: Int) => x * x

is expanded to:

  class AnonFun extends Function1[Int, Int] {
    def apply(x: Int) = x * x
  }
  new AnonFun

or, shorter, using anonymous class syntax:

  new Function1[Int, Int] {
    def apply(x: Int) = x * x
  }

Expansion of Function Calls

A function call, such as f(a, b), where f is a value of some class type, is expanded to:

  f.apply(a, b)

So the OO-translation of:

  val f = (x: Int) => x * x
  f(7)

would be:

  val f = new Function1[Int, Int] {
    def apply(x: Int) = x * x
  }
  f.apply(7)

map

Thus, instead of writing the following:

  xs.map(x => x + 1)

You can write:

  for (x <- xs) yield x + 1

You can read it as “for every value, that I name ‘x’, in ‘xs’, return ‘x + 1’”.

filter

Also, instead of writing the following:

  xs.filter(x => x % 2 == 0)

You can write:

  for (x <- xs if x % 2 == 0) yield x

The benefit of this syntax becomes more apparent when it is combined with the previous one:

  for (x <- xs if x % 2 == 0) yield x + 1

Equivalent to the following:

  xs.filter(x => x % 2 == 0).map(x => x + 1)

flatMap

Finally, instead of writing the following:

  xs.flatMap(x => ys.map(y => (x, y)))

You can write:

  for (x <- xs; y <- ys) yield (x, y)

You can read it as “for every value ‘x’ in ‘xs’, and then for every value ‘y’ in ‘ys’, return ‘(x, y)’”.

Variances: Covariance [+A], Contravariance [-A]


Definition of Variance

Roughly speaking, a type that accepts mutations of its elements should not be covariant.

But immutable types can be covariant, if some conditions on methods are met.

Say C[T] is a parameterized type and A, B are types such that A <: B.

In general, there are three possible relationships between C[A] and C[B]:

C[A] <: C[B], C is covariant,
C[A] >: C[B], C is contravariant,
neither C[A] nor C[B] is a subtype of the other, C is nonvariant.
Scala lets you declare the variance of a type by annotating the type parameter:

class C[+A] { … }, C is covariant,
class C[-A] { … }, C is contravariant,
class C[A] { … }, C is nonvariant.


和訳すると統計学の共分散と反共分散となりますが、Scalaでの意味は以下の具体例で理解した方がいいでしょう。

https://docs.scala-lang.org/tour/variances.html

Covariant C[+A]Contravariant C[-A] の例を以下に記述。

C, A 共に 任意のクラス(型) を示す。
AB の派生クラス (A <: B) とした場合。

Covariant C[+A]

C[A]C[B] の派生クラスと定義される。C[A] <: C[B]

クラス Cat, Dog(Aに該当) は、クラス Animal(Bに該当) の派生クラスなので、 List[Cat], List[Dog]List[Animal] の派生クラスと定義される。 この場合、List(Cに該当)Covariant となる。

Contravariant C[-A]

C[B]C[A] の派生クラスと定義される。C[A] >: C[B]

Printer[-A] により、クラス Printer[Animal]Printer[Cat] の派生クラスとして定義される。この場合、 Printer(Cに該当)Contravariant となる。

package example

object CoAndContravariant extends App {

// ここからCovariant [+A]の例
  abstract class Animal {
    def name: String
  }
  case class Cat(name: String) extends Animal
  case class Dog(name: String) extends Animal
// List[Animal] AnimalがCovariant
  def printAnimalNames(animals: List[Animal]): Unit =
    animals.foreach {
      animal => println(animal.name)
    }

  val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
  val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))

  // prints: Whiskers, Tom
  printAnimalNames(cats)

  // prints: Fido, Rex
  printAnimalNames(dogs)

//ここまでがConvariant [+A]の例 

//ここからContravariant [-A] の例

  abstract class Printer[-A] {
    def print(value: A): Unit
  }

  class AnimalPrinter extends Printer[Animal] {
    def print(animal: Animal): Unit =
      println("The animal's name is: " + animal.name)
  }

  class CatPrinter extends Printer[Cat] {
    def print(cat: Cat): Unit =
      println("The cat's name is: " + cat.name)
  }

  def printMyCat(printer: Printer[Cat], cat: Cat): Unit =
    printer.print(cat)

  val catPrinter: Printer[Cat] = new CatPrinter
  val animalPrinter: Printer[Animal] = new AnimalPrinter

  printMyCat(catPrinter, Cat("Boots"))
  printMyCat(animalPrinter, Cat("Boots"))
//ここまでContravariant [-A] の例

}

出力

Whiskers
Tom
Fido
Rex
The cat's name is: Boots
The animal's name is: Boots

Upper Type Bounds, Lower Type Bounds

[T <: A] : Tは、Aとその派生クラス(サブタイプ)のみ許容するクラスとして定義。
https://docs.scala-lang.org/tour/upper-type-bounds.html

Ex)

// Scala Program To Demonstrate Scala Upper Bound 
class  Principal 
class Teacher extends Principal 
class Student extends Teacher 
  
class School 
{ 
    // Declaration of upper bound 
    def display [T <: Teacher](t: T) 
    { 
        println(t) 
    } 
} 
  
// Object created 
object ScalaUpperBounds 
{ 
    // Driver code 
    def main(args: Array[String]) 
    { 
        // Defined new variable 
        val principal = new Principal 
        val teacher = new Teacher 
        val student = new Student 
  
        val school = new School 
          
        // school.display(principal) --->コンパイルエラー
        school.display(teacher) 
        school.display(student) 
  } 
} 

出力

Teacher@506e1b77
Student@4fca772d

[T >: A] : Tは、Aとその派生クラス(サブタイプ)だけでなく、Aの元クラスからの派生クラスも許容するクラスとして定義。

https://docs.scala-lang.org/tour/lower-type-bounds.html

Ex)

class Principal 
class Teacher extends Principal 
class Student extends Teacher 
  
class School 
{ 
    // Declaration of lower bound 
    def display [T >: Teacher](t: T) 
    { 
        println(t) 
    } 
} 
  
// Object created 
object ScalaUpperBounds 
{ 
    // Driver code 
    def main(args: Array[String]) 
    { 
        // Defined new variable 
        val principal = new Principal 
        val teacher = new Teacher 
        val student = new Student 
  
        val school = new School 
          
        school.display(principal) 
        school.display(teacher) 
        school.display(student) 
} 
} 

出力

Principal@506e1b77
Teacher@4fca772d
Student@9807454