<scala class 예시 1> 다음 글이다.

http://[scala] class 예시 1 - 일반 클래스, 싱글톤 클래스(singleton class), 케이스 클래스(case class), main메소드/App 상속 클래스, 합성 타입(Compound), 제네릭(generic) 클래스






scala의 implicit은 다른 언어에서도 볼 수 있는 개념이지만, 더 큰 느낌이다.. C++언어와 C#에서 사용했던 클래스의 연산자(unary operator)를 재정의와 흡사하다.  하지만, 더 크게 보면, 클래스간 타입을 변환할 수 있다. (이런 언어 기능은 다른 언어에서도 본 적이 없다)


전에 (클래스 + 3)와 (3 + 클래스)와 같은 형태의 지원에 대해서는 아래 링크에서 조금 다뤘다. 

http://knight76.tistory.com/entry/scala-%EB%AC%B5%EC%8B%9C%EC%A0%81-%ED%83%80%EC%9E%85-%EB%B3%80%ED%99%98)



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)



만약 타입이 다른 implicit 매개변수를 추가하면 어떤 일이 발생할까?
def plus(i: Int) (implicit j: Int, k: Long) = {
println("k:" + k)
println(i + j + k)
}
implicit val j = 5
plus(10)

역시 could not find implicit value for parameter k: Long라는 런타임 에러가 발생한다.
아래와 같이 수정해서 실행한다.

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)
결과는 다음과 같다.

k:10
25


매개변수를 모두 implicit을 주면 에러가 발생한다. 

def plus(implicit i: Int, j: Int) = {
println(i + j)
}
implicit val i = 5
implicit val j = 10
plus

에러 내용은 다음과 같다. 
ambiguous implicit values:
 both value i in object Main of type => Int
 and value j in object Main of type => Int
 match expected type Int


타입을 바꿔서 테스트한다.

def plus(implicit i: Int, j: Long) = {
println(i + j)
}
implicit val i = 5
implicit val j = 10L
plus
실행결과는 의도된대로 15이다. 


Future 클래스가 대표적인 implicit 매개변수 예시라 한다. (공부 더 해야지..)

http://docs.scala-lang.org/overviews/core/futures.html


  1. implicit val ec: ExecutionContext = ...
  2. val inverseFuture : Future[Matrix] = Future {
  3. fatMatrix.inverse()
  4. } // ec is implicitly passed





이제, class 3 으로 넘어간다. 


[scala] class 3 - 믹스인(mixin) 클래스, AnyVal, 유니버셜 트레이트(trait), abstract type


Posted by '김용환'
,