[scala] class 예시 2 - 암시적 변환(implicit conversion - implicit converter/implicit class/implicit parameter)
scala 2016. 9. 21. 15:07scala의 implicit은 다른 언어에서도 볼 수 있는 개념이지만, 더 큰 느낌이다.. C++언어와 C#에서 사용했던 클래스의 연산자(unary operator)를 재정의와 흡사하다. 하지만, 더 크게 보면, 클래스간 타입을 변환할 수 있다. (이런 언어 기능은 다른 언어에서도 본 적이 없다)
전에 (클래스 + 3)와 (3 + 클래스)와 같은 형태의 지원에 대해서는 아래 링크에서 조금 다뤘다.
implicit converter부터 살펴본다.
class CompanyMember(val id: Int)
class SchoolMember(val id: Int)
3개의 클래스는 부모 클래스가 동일하지 않을 뿐더라 아예 호환할 수 없는 클래스이기에, 아래와 같은 코드를 짜면 type mismatch 에러가 발생한다.
val nationalMember : SchoolMember = CompanyMember(1)
비슷하게 구현하려면 trait나 abstract class를 써야 할 것이다.
암시적 변환은 일종의 숨겨진 함수 정도로 보면 된다. 표현식을 특정 타입으로 할당할 때, 잘 안되면, 범위(scope)에 있는 implicit type converter를 찾는다. 만약 implicit type converter가 있다면 그 코드로 변환한다.
이해할 수 있도록, 간단한 예제를 진행해 본다. 아래 코드는 컴파일만 되도록 한 코드이다.
아까처럼 CompanyMember와 SchoolMember 케이스 클래스를 그대로 쓴다.
case class CompanyMember(val id: Int)
case class SchoolMember(val id: Int)
SchoolMember 클래스를 매개변수로 받아 CompnayMember 타입으로 리턴하는 메소드를 정의하고, 간단하게 사용한 후, 출력한다.
def schoolMemberToCompanyMember(member: SchoolMember) : CompanyMember
= CompanyMember(member.id)
val sm : CompanyMember = schoolMemberToCompanyMember(SchoolMember(10))
println(sm.id)
결과는 10이다. 정상이다.
이제 implicit을 메소드 앞에 붙이고, 해당 메소드를 사용하는 부분에서 보이지 않도록 삭제한다.
implicit def schoolMemberToCompanyMember(member: SchoolMember) : CompanyMember
= CompanyMember(member.id)
//val sm : CompanyMember = schoolMemberToCompanyMember(SchoolMember(10))
val sm : CompanyMember = SchoolMember(10)
println(sm.id)
실행 결과는 10이다. 동일한 결과가 나온다. (확 감이 오지 않은가?)
여기다가 하나 더 추가해 보자.. NationalMember를 추가해서 m이라는 멤버 필드을 두자.
case class CompanyMember(val id: Int)
case class SchoolMember(val id: Int)
case class NationalMember(val id: Int) {
val m = id + 100000
}
실행 코드는 추가해본다.
implicit def schoolMemberToCompanyMember(member: SchoolMember) : CompanyMember = CompanyMember(member.id)
implicit def companyMemberToNationalMember(member: CompanyMember) : NationalMember = NationalMember(member.id)
val sm : CompanyMember = SchoolMember(10)
println(sm.id)
val a : NationalMember = new CompanyMember(1)
println(a.m)
결과는 다음과 같다.
10
100001
implicit type conveter는 scope가 같아야 한다. 따라서 같은 scope에 두거나 import 문을 다음처럼 써야 한다.
case class CompanyMember(val id: Int)
case class SchoolMember(val id: Int)
case class NationalMember(val id: Int) {
val m = id + 100000
}
object ImplicitRef {
implicit def schoolMemberToCompanyMember(member: SchoolMember) : CompanyMember = CompanyMember(member.id)
implicit def companyMemberToNationalMember(member: CompanyMember) : NationalMember = NationalMember(member.id)
}
object Main extends App {
import com.google.ImplicitRef._
val sm : CompanyMember = SchoolMember(10)
println(sm.id)
val a : NationalMember = new CompanyMember(1)
println(a.m)
}
결과는 위와 동일하다.
만약, 두 개의 implicit def 변환 중 schoolMember만 CompanyMember의 변환만 사용하고 싶다면, 다음과 같이 명확하게 정한다. 그래서 의도에 맞게 개발할 수 있다.
import com.google.ImplicitRef.schoolMemberToCompanyMember
val sm : CompanyMember = SchoolMember(10)
println(sm.id)
val a : NationalMember = new CompanyMember(1) // type mismatch error
println(a.m)
스칼라의 묵시적 변환을 이해할 수 있는 코드를 살펴본다.
scala.Predef 클래스를 참조하면, 묵시적 변환 코드가 있다.
// "Autoboxing" and "Autounboxing" ---------------------------------------------------
implicit def byte2Byte(x: Byte) = java.lang.Byte.valueOf(x)
implicit def short2Short(x: Short) = java.lang.Short.valueOf(x)
implicit def char2Character(x: Char) = java.lang.Character.valueOf(x)
implicit def int2Integer(x: Int) = java.lang.Integer.valueOf(x)
implicit def long2Long(x: Long) = java.lang.Long.valueOf(x)
implicit def float2Float(x: Float) = java.lang.Float.valueOf(x)
implicit def double2Double(x: Double) = java.lang.Double.valueOf(x)
implicit def boolean2Boolean(x: Boolean) = java.lang.Boolean.valueOf(x)
implicit def Byte2byte(x: java.lang.Byte): Byte = x.byteValue
implicit def Short2short(x: java.lang.Short): Short = x.shortValue
implicit def Character2char(x: java.lang.Character): Char = x.charValue
implicit def Integer2int(x: java.lang.Integer): Int = x.intValue
implicit def Long2long(x: java.lang.Long): Long = x.longValue
implicit def Float2float(x: java.lang.Float): Float = x.floatValue
implicit def Double2double(x: java.lang.Double): Double = x.doubleValue
implicit def Boolean2boolean(x: java.lang.Boolean): Boolean = x.booleanValue
다음은 Option 예제이다. 아래 코드를 대충 보면 어이없는 코드이지만, Iterator와 Option을 비교한 값이다. 실행도 되는 코드이다. 타입이 다른데 비교가 가능한 이유가 바로 implicit 이다.
val name = Iterator("Samuel", "Santos")
val iterator = name.toIterator
if (iterator != Option("Samuel")) {
System.out.println("11")
}
Option object 소스에 implicit def option2Iterable이 있어서 Option을 Iterator와 비교할 수 있었다.
object Option {
import scala.language.implicitConversions
/** An implicit conversion that converts an option to an iterable value
*/
implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList
다음은 implicit class를 살펴본다. (이전까지 implicit conveter를 다뤘다)
String 객체에 대해서 암시적인 변환을 진행하는 코드인데, 클래스 기반이 되도록 하는 예시를 살펴본다.
implicit class를 선언할 때 non-implicit인 매개변수가 최소 하나가 반드시 있어야 한다.
implicit class Member(val _name: String) {
def name = "member name : " + _name
}
println("samuel".name)
결과는 다음과 같다.
member name : samuel
코드를 깔끔히 하기 위해서 아래와 같이 썼더니. wrong top statement declaration 컴파일 에러가 발생한다. object로 감싸야 한다.
implicit class Member(val _name: String) { .. }
wrapper object를 하나 만들어서 implicit class를 감싸게 한다. trait, class, object에 감싸야 한다. 이제 컴파일이 된다.
object MemberWrapper {
implicit class Member(val _name: String) {
def name = "member name : " + _name
}
}
object Main extends App {
import com.google.MemberWrapper._
println("samuel".name)
}
코드를 실행하면 다음과 같다.
member name : samuel
래퍼 클래스에서 implicit class를 추가할 수 있으며, 실행할 수 있다.
object MemberWrapper {
implicit class Member(val _name: String) {
def name = "member name : " + _name
}
implicit class Person(val _id: Int) {
def name ="Person id : " + _id
}
}
object Main extends App {
import com.google.MemberWrapper._
println("samuel".name)
println(1.name)
}
필드를 사용한 예시에서 함수로 바꿔본다.
object MemberWrapper {
implicit class Member(val _name: String) {
def method[A](f: => A) : String = {
f
"member name : " + _name
}
}
}object Main extends App {
import com.google.MemberWrapper._
println("samuel" method println("this function"))
}
결과는 다음과 같다.
this function
member name : samuel
이제, 함수를 호출할 때 매개변수를 생략할 수 있는 implicit parameter를 살펴본다.
implicit 매개변수가 있는 간단한 메소드이다. 메소드 매개변수 이름이 정확히 맞아야 한다. method 메소드는 i라는 변수를 기반으로 추측할 수 있다.
def method(implicit i: Int) = println(i)
implicit val i = 10
method
중요한 것은 implicit modifier가 반드시 붙어야 동작한다.
만약 implicit modifier을 사용하지 않으면, could not find implicit value for parameter i: Int 이라는 런타임 에러가 발생한다.
첫 매개변수는 non-implicit이고 두 번째 매개변수를 implicit로 정의하려면, (..., implicit ...) 이 아닌
(.. )(implicit .. )으로 나눠야 한다. 이렇게 안하면 컴파일 에러가 발생한다.
def plus(i: Int) (implicit j: Int) = println(i + j)
implicit val j = 20
plus(10)
결과는 예상대로 30이다.
첫 매개변수는 non-implicit이고 두 번째, 세 번째 매개변수는 implicit를 정의한다면, 다음과 같을 것이다.
def plus(i: Int) (implicit j: Int, k: Int) = {
println("k:" + k)
println(i + j + k)
}
implicit val j = 20
plus(10)
결과는 쇼킹하다. implicit 값을 k에도 저장했다. 결과는 다음과 같다.
k:20
50
def plus(i: Int) (implicit j: Int, k: Int, x:Int) = {
println("k:" + k)
println("x:" + x)
println(i + j + k + x)
}
implicit val j = 5
plus(10)
def plus(i: Int) (implicit j: Int, k: Long) = {
println("k:" + k)
println(i + j + k)
}
implicit val j = 5
plus(10)
def plus(i: Int) (implicit j: Int, k: Long) = {
println("k:" + k)
println(i + j + k)
}
implicit val j = 5
implicit val k = 10L
plus(10)
def plus(implicit i: Int, j: Int) = {
println(i + j)
}
implicit val i = 5
implicit val j = 10
plus
def plus(implicit i: Int, j: Long) = {
println(i + j)
}
implicit val i = 5
implicit val j = 10L
plus
implicit val ec: ExecutionContext = ...
val inverseFuture : Future[Matrix] = Future {
fatMatrix.inverse()
} // ec is implicitly passed
이제, class 3 으로 넘어간다.
[scala] class 3 - 믹스인(mixin) 클래스, AnyVal, 유니버셜 트레이트(trait), abstract type
'scala' 카테고리의 다른 글
[scala] class 3 - 믹스인(mixin) 클래스, AnyVal, 유니버셜 트레이트(trait), abstract type (0) | 2016.09.22 |
---|---|
[scala] scala 코드 역어셈블링하기 (부제 : scala 코드 분석하기) (0) | 2016.09.22 |
[scala] class 예시 1 - 일반 클래스, 싱글톤 클래스(singleton class), 케이스 클래스(case class), main메소드/App 상속 클래스, 합성 타입(Compound), 제네릭(generic) 클래스 (0) | 2016.09.20 |
[scala] List 예시 - 2 (0) | 2016.09.19 |
[scala] List 정의와 예시 (0) | 2016.09.13 |