-
Notifications
You must be signed in to change notification settings - Fork 7
5.2.5_8
Seo Inhyuk edited this page Sep 5, 2016
·
25 revisions
- 암시를 과도하게 사용할 경우 코드를 읽는 사람이 이해하기 어려울 수 있음 -> 암시를 필요할때만 현명하게 사용하는 것이 필요
- 암시를 사용하는 이유
(1) 준비를 위한 코드를 없앰 (ex. context 정보)
(2) 매개변수화한 타입을 받는 메서드에 사용 -> 버그를 줄이거나 허용되는 타입을 제한하기 위한 제약사항
- 암시적 증거(implicit evidence) : 허용할 타입을 제한하면서, 각 타입이 공통 슈퍼타입을 따를 필요가 없음
- 타입 소거(type erasure)로 인한 제약을 우회하는 기법 : 암시적 객체가 증거 역할만을 함
// 1
scala> object M {
| def m(seq: Seq[Int]): Unit = println(s"Seq[Int]: $seq")
| def m(seq: Seq[Double]): Unit = println(s"Seq[Double]: $seq")
| }
<console>:12: error: double definition:
def m(seq: Seq[Int]): Unit at line 11 and
def m(seq: Seq[Double]): Unit at line 12
have same type after erasure: (seq: Seq)Unit
def m(seq: Seq[Double]): Unit = println(s"Seq[Double]: $seq")
// 2
scala> def m(seq: Seq[Int]): Unit = println(s"Seq[Int]: $seq")
m: (seq: Seq[Int])Unit
scala> def m(seq: Seq[Double]): Unit = println(s"Seq[Double]: $seq")
m: (seq: Seq[Double])Unit
- JVM은 매개변수화한 타입의 타입인자를 '망각' (타입 소거, type erasure) -> 1과 같이 타입정보를 제거하여 오버로딩한 두 메서드가 같은 메서드라고 판단
- 2와 같이 REPL에서는 편의상 타입이나 값, 메서드를 오버라이딩 할 수 있도록 허용하기 때문에 오류가 발생하지 않음
- 스칼라 컴파일러는 바이트 코드 상에서 두 메서드가 동일하다고 판단하기 때문에, 이러한 모호한 정의를 금지함
object M {
implicit object IntMarker
implicit object StringMarker
def m(seq: Seq[Int])(implicit i: IntMarker.type ): Unit =
println(s"Seq[Int]: $seq")
def m(seq: Seq[String])(implicit s: StringMarker.type ): Unit =
println(s"Seq[String]: $seq")
}
import M._
m(List(1,2,3))
m(List("one", "two", "three"))
Seq[Int]: List(1, 2, 3)
res0: Unit = ()
Seq[String]: List(one, two, three)
res1: Unit = ()
- 스칼라 컴파일러가 수행하는 타입 소거로 인한 메서드의 모호성을 제거하기 위해 암시적 인자를 활용
- 모호성을 제거해 메서드를 구분하기 위해 사용할 암시적 객체인 IntMarker, StringMarker
- .type : 싱글턴 객체의 타입을 참조
- [궁금한 사항] IntMarker.type, StringMarker.type으로 각 매개변수화한 타입의 타입인자를 구분하는 것 같은데, IntMarker,StringMarker가 아닌 다른 이름으로 해도 결과에 변함이 없습니다. 암시적 객체 선언시 타입을 부여한 것도 아니고, IntMarker, StringMarker가 정의되어있는 키워드나 객체도 아닌데 각각 Int와 String으로 참조를 어떻게 하는지 궁금합니다.
- 암시적 인자에 Int나 String 같이 직접적으로 일반적인 타입을 사용하지 않는 이유
- 첫번째 모듈 : 현재 범위 안에 String 타입의 암시적 '기본'값을 정의해두고, String 타입의 암시적 인자를 사용
- 두번째 모듈 : 첫번째 모듈의 범위 안에서 자신만의 암시적 String 인자를 정의
(1) 두번째 모듈에서 암시적'기본'값을 정의하지 않고, 사용자가 애플리케이션에 따라 적절한 기본값을 정의하도록 하는 경우 -> 사용자가 기본값을 정의하지 않는 경우, 다른 모듈의 암시적 String을 사용하게 되고, 프로그램이 예상치 못한 동작을 할 수 있음
(2) 사용자가 두번째 암시적 값을 정의하는 경우 범위안에 타입이 같은 두 암시적 값이 존재하여 모호성이 생김 -> 컴파일 오류 발생
- [궁금한 사항] 바로 위에 설명한 암시적 인자에 일반적인 타입을 사용하지 않는 이유에 대해 자세히 알고싶습니다. 예제코드를 찾아보려고 했는데 쉽지가 않네요..
- CanBuildFrom : 시작할 때 사용했던 컬렉션과 동일한 타입의 컬렉션을 반환하고 싶은 경우 전달하는 빌더
- CanBuildFrom 정의가 존재하지 않는 타입에 map을 사용
scala> case class ListWrapper(list: List[Int])
defined class ListWrapper
scala> List(1,2,3).map[Int, ListWrapper](_ * 2)
<console>:14: error: Cannot construct a collection of type ListWrapper with elements of type Int based on a collection of type List[Int].
List(1,2,3).map[Int, ListWrapper](_ * 2)
- 위 메시지는 컴파일러가 암시적 인자에 대한 암시적 값을 찾을 수 없을때 일반적으로 내놓는 오류 메시지와 다름!
- scala.annotation.implicitNotFound 애노테이션을 암시적 인자를 만족시키기 위해 암시적 값으로 사용할 의도가 있는 타입에 붙여야 함
- 유령 타입(Phantom Type) : 타입은 존재하지만 인스턴스 정의는 없는 타입
- 암시와는 관계가 없지만, 어떠한 일을 순차적으로 혹은 특정 순서로 진행해야 할 경우 작업흐름을 정의할 때 유용함
- 유령타입은 타입이 존재하는지 존재여부만 확인함 -> '표식'
// 기본적인 Phantom Type 사용
package progscala2.implicits.payroll
// 봉인된 트레이트 사용 => 다른 파일에서 구현할 수 없는 '표식'의 역할
sealed trait PreTaxDeductions
sealed trait PostTaxDeductions
sealed trait Final
case class Employee(
name: String,
annualSalary: Float,
taxRate: Float,
insurancePremiumsPerPayPeriod: Float,
_401kDeductionRate: Float,
postTaxDeductions: Float)
case class Pay[Step](employee: Employee, netPay: Float)
// Payroll 객체에서 선언된 순서대로 메서드를 호출해야 함!
object Payroll {
// employee -> Pay[PreTaxDeductions]
def start(employee: Employee): Pay[PreTaxDeductions] =
Pay[PreTaxDeductions](employee, employee.annualSalary / 26.0F)
// Pay[PreTaxDeductions] -> Pay[PreTaxDeductions]
def minusInsurance(pay: Pay[PreTaxDeductions]): Pay[PreTaxDeductions] = {
val newNet = pay.netPay - pay.employee.insurancePremiumsPerPayPeriod
pay copy (netPay = newNet)
}
// Pay[PreTaxDeductions] -> Pay[PreTaxDeductions]
def minus401k(pay: Pay[PreTaxDeductions]): Pay[PreTaxDeductions] = {
val newNet = pay.netPay - (pay.employee._401kDeductionRate * pay.netPay)
pay copy (netPay = newNet)
}
// Pay[PreTaxDeductions] -> Pay[PostTaxDeductions]
def minusTax(pay: Pay[PreTaxDeductions]): Pay[PostTaxDeductions] = {
val newNet = pay.netPay - (pay.employee.taxRate * pay.netPay)
pay copy (netPay = newNet)
}
// Pay[PostTaxDeductions] -> Pay[Final]
def minusFinalDeduction(pay: Pay[PostTaxDeductions]): Pay[Final] = {
val newNet = pay.netPay - pay.employee.postTaxDeductions
pay copy (netPay = newNet)
}
}
object CalculatePayroll {
def main(args: Array[String]) = {
val e = Employee("inhack", 100000.0F, 0.25F, 200F, 0.10F, 0.05F)
val pay1 = Payroll start e
val pay2 = Payroll minus401k pay1
val pay3 = Payroll minusInsurance pay2
val pay4 = Payroll minusTax pay3
val pay = Payroll minusFinalDeduction pay4
val twoWeekGross = e.annualSalary / 26.0F
val twoWeekNet = pay.netPay
val percent = (twoWeekNet / twoWeekGross) * 100
println(s"For ${e.name}, the gross vs. net pay every 2 weeks is:")
println(f"$$${twoWeekGross}%.2f vs. $$${twoWeekNet}%.2f of ${percent}%.1f%%")
}
}
// Pipeline 연산자를 활용
package progscala2.implicits.payroll
object Pipeline {
implicit class toPIped[V](value: V){
def |>[R] (f: V => R) = f(value)
}
}
object CalculatePayroll2{
def main(args: Array[String]) = {
import Pipeline._
import Payroll._
val e = Employee("inhack", 100000.0F, 0.25F, 200F, 0.10F, 0.05F)
val pay = start(e) |>
minus401k |>
minusInsurance |>
minusTax |>
minusFinalDeduction
val twoWeekGross = e.annualSalary / 26.0F
val twoWeekNet = pay.netPay
val percent = (twoWeekNet / twoWeekGross) * 100
println(s"For ${e.name}, the gross vs. net pay every 2 weeks is:")
println(f"$$${twoWeekGross}%.2f vs. $$${twoWeekNet}%.2f of ${percent}%.1f%%")
}
}
- 위 코드의 파이프라인( |> )은 단순히 토큰을 재배열하는 역할을 함
- pay1 |> Payroll.minus401k --> Payroll.minus401k(pay1)
- 암시적 인자 처리 규칙
- (1) 마지막 인자 목록에만 암시적 인자가 들어갈 수 있음
scala> class Bad1 {
| def m(i: Int, implicit s: String) = "boo"
<console>:2: error: identifier expected but 'implicit' found.
def m(i: Int, implicit s: String) = "boo"
^
scala> class Bad2 {
| def m(i: Int)(j:Int, implicit s: String) = "boo"
<console>:2: error: identifier expected but 'implicit' found.
def m(i: Int)(j:Int, implicit s: String) = "boo"
- (2) implicit 키워드는 인자 목록의 맨 처음에 와야하며, 한번만 사용할 수 있음 / 인자 목록 안에서 암시적 인자 다음에 비암시적 인자가 위치할 수 없음
scala> class Bad3 {
| def m(i: Int)(implicit s1: String)(implicit s2: String) = "boo"
<console>:2: error: '=' expected but '(' found.
def m(i: Int)(implicit s1: String)(implicit s2: String) = "boo"
^
scala> class Bad4 {
| def m(i: Int)(implicit s: String)(j: Int) = "boo"
<console>:2: error: '=' expected but '(' found.
def m(i: Int)(implicit s: String)(j: Int) = "boo"
- (3) 인자 목록이 implicit 키워드로 시작할 경우, 그 인자 목록 안의 모든 인자는 암시적 인자가 됨
scala> class Good1 {
| def m(i: Int)(implicit s1: String, d: Double, s2: String) = "boo"
| }
defined class Good1