일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- hackthissite
- BOF
- c++
- hackerschool
- 웹해킹
- flask
- 챗GPT
- Javascript
- BOF 원정대
- 러닝 스칼라
- Scala
- Linux
- 러닝스칼라
- backend
- webhacking
- deep learning
- Web
- 딥러닝
- 파이썬
- mysql
- 리눅스
- 인공지능
- 백엔드
- Shellcode
- c
- ChatGPT
- Python
- 경제
- php
- hacking
- Today
- Total
jam 블로그
러닝스칼라 10장 - 고급 타입 특징 본문
스칼라 데이터 타입 특징을 깊게 알아보자.
이 장을 이해함으로써 스칼라 API, 라이브러리 동작 방식을 이해할 수 있다.
튜플과 함숫값 클래스
튜플은 TupleX[Y]
케이스 클래스의 인스턴스로 구현
X
: 1 <= X <= 22, 튜플의 입력 매개변수 개수
Y
: 타입 매개변수
productArity
: 튜플의 매개변수 개수를 반환하는 함수, 튜플이 상속받는 ProductX 트레이트에 정의됨
scala> val x = (10, 20)
x: (Int, Int) = (10,20)
scala> val x: (Int, Int) = Tuple2(10, 20)
x: (Int, Int) = (10,20)
scala> println("Does the arity = 2? " + (x.productArity == 2))
Does the arity = 2? true
함수값은 FunctionX[Y]
트레이트를 확장한 익명 클래스의 인스턴스로 구현된다.
X
: 매개변수 개수
Y
: 타입 매개변수, 첫 번째는 반환값 타입, 두 번째부터는 매개변수의 타입
스칼라 컴파일러가 함수 리터럴을 FunctionX를 확장하는 새로운 클래스의 apply() 메소드의 body로 전환한다.
이는 모든 함수가 클래스 메소드로 구현되도록 제한하는 JVM과 호환되도록 해준다.
scala> val hello1 = (n: String) => s"Hello, $n"
hello1: String => String = $$Lambda$839/0x000000080104a040@3820cfe
scala> val h1 = hello1("Function Literals")
h1: String = Hello, Function Literals
scala> val hello2 = new Function1[String, String] {
| def apply(n: String) = s"Hello, $n"
| }
hello2: String => String = <function1>
scala> val h2 = hello2("Functional Instances")
h2: String = Hello, Functional Instances
scala> println(s"hello1 = $hello1, hello2 = $hello2")
hello1 = $line6.$read$$iw$$iw$$$Lambda$839/0x000000080104a040@3820cfe, hello2 = <function1>
Function1 트레이트는 FunctionX (X != 1)에는 없는 2개의 메소드를 가지고 있다.
andThen
: 두 함수값으로부터 새로운 함수값을 생성, 왼쪽 인스턴스에서 오른쪽에 있는 인스턴스를 실행
compose
: 반대 방향으로 동작
scala> val doubler = (i: Int) => i*2
doubler: Int => Int = $$Lambda$970/0x00000008010c3040@22b10124
scala> val plus3 = (i: Int) => i+3
plus3: Int => Int = $$Lambda$971/0x00000008010c3840@79ae3fb1
scala> val append = (doubler andThen plus3)(1)
append: Int = 5
scala> val prepend = (doubler compose plus3)(1)
prepend: Int = 8
묵시적 매개변수 (implicit parameter)
nonimplicit 매개변수와 별개로 implicit한 매개변수 그룹을 정의한다.
implicit 매개변수에 명시적으로 값을 넣어도 동작한다.
scala> object Doubly {
| def print(num: Double)(implicit fmt: String) = {
| println(fmt format num)
| }
| }
defined object Doubly
scala> case class USD(amount: Double) {
| implicit val printFmt = "%.2f"
| def print = Doubly.print(amount)
| }
defined class USD
scala> new USD(81.924).print
81.92
scala> Doubly.print(3.724)("%.1f")
3.7
묵시적 클래스 (implicit class)
묵시적 클래스는 특정 클래스의 자동 전환을 제공
// Int를 Fishes 클래스로 자동 전환
scala> object IntUtils {
| implicit class Fishies(val x: Int) {
| def fishes = "Fish" * x
| }
| }
defined object IntUtils
scala> import IntUtils._
import IntUtils._
scala> println(3.fishes)
FishFishFish
묵시적 클래스 제약 사항
- 다른 객체, 클래스, 트레이트 안에 정의되어야 한다.
- 하나의 nonimplicit 클래스 인수를 받아야 한다.
- 현재 네임스페이스의 다른 객체, 클래스, 트레이트와 클래스명이 겹치면 안된다.
케이스 클래스는 자동으로 생성된 동반 객체와 이름이 겹치기 때문에 묵시적 클래스로 사용할 수 없다.
객체 내에 구현하는 것이 가장 좋다. 객체 내에 정의할 경우 쉽게 임포트할 수 있다.
여러가지 묵시적 클래스를 가진 scala.Predef
객체는 자동으로 네임스페이스에 추가된다.
예를 들어서 x -> y 연산자는 아래와 같이 정의되어 자동으로 적용된다.
// 간략하게 적음
// 1 -> "a" Tuple2(1,"a")
implicit class ArrowAssoc[A](x: A) {
def ->[B](y: B) = Tuple2(x, y)
}
타입
클래스, 트레이트 타입은 사용가능하지만 객체는 타입으로 간주하지 않는다.
scala> class A
defined class A
scala> trait B
defined trait B
scala> val a:A = new A
a: A = A@4860627a
scala> val b:B = new B{}
b: B = $anon$1@79486f38
타입 별칭 (type alias)
기존 타입을 새롭게 명명하는 것
타입 별칭은 객체, 클래스, 트레이트 내부에서만 정의가능
객체는 타입 별칭을 생성하는 데 사용할 수 없음
type <식별자>[타입 매개변수] = <타입명>[타입 매개변수]
scala> object TypeFun {
| type Whole = Int
| val x: Whole = 5
|
| type UserInfo = Tuple2[Int, String]
| val u: UserInfo = new UserInfo(123, "George")
|
| type T3[A,B,C] = Tuple3[A,B,C]
| val things = new T3(1, 'a', true)
| }
defined object TypeFun
scala> val x = TypeFun.x
x: TypeFun.Whole = 5
scala> val u = TypeFun.u
u: TypeFun.UserInfo = (123,George)
scala> val things = TypeFun.things
things: (Int, Char, Boolean) = (1,a,true)
추상 타입 (abstract type)
추상 클래스, 트레이트를 mixin한 서브클래스가 구현해야만 하는 타입을 선언하기 위해 사용
허용 가능한 타입의 범위를 지정하기 위해 타입 매개변수로 사용된다.
인스턴스를 생성하는데 사용할 수 없다.
scala> class User(val name: String)
defined class User
scala> trait Factory { type A; def create: A }
defined trait Factory
scala> trait UserFactory extends Factory {
| type A = User
| def create = new User("")
| }
defined trait UserFactory
//위의 코드와 동일하기 동작하지만 더 간결함
scala> trait Factory[A] { def create: A }
defined trait Factory
scala> trait UserFactory extends Factory[User] { def create = new User("") }
defined trait UserFactory
경계가 있는 타입 (bounded type)
상한 경계
<식별자> <: <상한 경계 타입>
해당 타입 또는 서브타입으로 제한
scala> class BaseUser (val name: String)
defined class BaseUser
scala> class Admin(name: String, val level: String) extends BaseUser(name)
defined class Admin
scala> class Customer(name: String) extends BaseUser(name)
defined class Customer
scala> class PreferredCustomer(name: String) extends Customer(name)
defined class PreferredCustomer
check: [A <: BaseUser](u: A)Unit
scala> check(new Customer("Fred"))
scala> check(new Admin("", "strict"))
Fail!
하한 경계
<식별자> >: <하한 경계 타입>
해당 타입 또는 그 타입이 확장한 타입으로 제한
scala> def recruit[A >: Customer](u: Customer): A = u match {
| case p: PreferredCustomer => new PreferredCustomer(u.name)
| case u: Customer => new Customer(u.name)
| }
recruit: [A >: Customer](u: Customer)A
scala> val customer = recruit(new Customer("Fred"))
customer: Customer = Customer@52d7889a
scala> val preferred = recruit(new PreferredCustomer("George"))
preferred: Customer = PreferredCustomer@32dd05af/
scala> abstract class Card {
| type UserType <: BaseUser
| def verify(u: UserType): Boolean
| }
defined class Card
scala> class SecurityCard extends Card {
| type UserType = Admin
| def verify(u: Admin) = true
| }
defined class SecurityCard
scala> val v1 = new SecurityCard().verify(new Admin("George", "high"))
v1: Boolean = true
타입 가변성 (type variance)
타입 가변성을 사용하면 타입 매개변수를 더 유연하게 사용할 수 있다.
scala> class Car { override def toString = "Car()" }
defined class Car
scala> class Volvo extends Car { override def toString = "Volvo()" }
defined class Volvo
// Volvo 인스턴스가 타입 Car의 값으로 할당될 수 있음
scala> val c: Car = new Volvo()
c: Car = Volvo()
// Item[Volvo] 인스턴스는 Item[Car]의 값으로 할당될 수 없다.
scala> val c: Item[Car] = new Item[Volvo](new Volvo)
^
error: type mismatch;
found : Item[Volvo]
required: Item[Car]
Note: Volvo <: Car, but class Item is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
타입 매개변수 앞에 +
를 붙이면 공변(covariant)하게 만들 수 있다.
공변성은 타입 매개변수가 그 타입이 확장한 타입으로 변할 수 있는 성질
scala> case class Item[+A](a: A) { def get: A = a }
defined class Item
scala> val c: Item[Car] = new Item[Volvo](new Volvo)
c: Item[Car] = Item(Volvo())
scala> val auto = c.get
auto: Car = Volvo()
타입 매개변수 앞에 -
를 붙이면 반공변(contravariant)하게 만들 수 있다.
반공변성은 타입 매개변수가 서브타입으로 변할 수 있는 성질
메소드 매개변수에 사용된 타입 매개변수는 반공변성을 가진다.
scala> class Check[+A] { def check(a: A) = {} }
^
error: covariant type A occurs in contravariant position in type A of value a
scala> class Check[-A] { def check(a: A) = {} }
defined class Check
scala> class Car; class Volvo extends Car; class VolvoWagon extends Volvo
defined class Car
defined class Volvo
defined class VolvoWagon
scala> class Item[+A](a: A) { def get: A = a }
defined class Item
scala> class Check[-A] { def check(a: A) = {} }
defined class Check
scala> def item(v: Item[Volvo]) { val c: Car = v.get }
item: (v: Item[Volvo])Unit
scala> def check(v: Check[Volvo]) { v.check(new VolvoWagon()) }
check: (v: Check[Volvo])Unit
모든 타입 매개변수를 불변의 상태로 유지하는 것이 더 안전하다.
패키지 객체 (package object)
패키지 객체는 해당 패키지에 위치한 파일 package.scala에 정의되어 네임스페이스에 자동 임포드된다.
// 위치: com/oreilly/package.scala
package object oreilly {
type Mappy[A, B] = collection.mutable.HashMap[A, B]
}
'IT Book Study > 러닝 스칼라' 카테고리의 다른 글
러닝스칼라 9장 - 객체, 케이스 클래스, 트레이트 (0) | 2020.09.28 |
---|---|
러닝스칼라 8장 - 클래스 (0) | 2020.09.28 |
러닝스칼라 7장 - 그 외의 컬렉션 (0) | 2020.09.28 |
러닝스칼라 6장 - 보편적인 컬렉션 (0) | 2020.09.28 |
러닝스칼라 5장 - 일급 함수 (0) | 2020.09.28 |