스칼라에서 패턴 매칭 코드를 사용시 주의해야 할 것이다. 


먼저 간단한 패턴 매칭 코드를 테스트해보자. 


object Main extends App {
def matchTest(x: Int): String = x match {
case 1 => "1"
case 2 => "2"
case _ => "3"
}
println(matchTest(3))
}

이 코드를 scala 바이트 코드로 생성하면, 간단한 java match로 변환한다. 이런 경우는 특별히 주의할 사항은 없다.



def matchTest(x: Int): String = {

      case <synthetic> val x1: Int = x;

      (x1: Int) match {

        case 1 => "1"

        case 2 => "2"

        case _ => "3"

      }

    };






만약, String 객체라면 어떨까?


object Main extends App {
def matchTest(x: String): String = x match {
case "1" => "1"
case "2" => "2"
case _ => "3"
}
println(matchTest("3"))
}


순차적인 if 문과 메소드가 생성된다. 



  case5(){

    if (x1.$isInstanceOf[Count]())

      matchEnd4(true)

    else

      case6()

  };

  case6(){

    matchEnd4(false)

  };

  matchEnd4(x: Boolean){

    x

  }


...


    def matchTest(x: String): String = {

      case <synthetic> val x1: String = x;

      case6(){

        if ("1".==(x1))

          matchEnd5("1")

        else

          case7()

      };

      case7(){

        if ("2".==(x1))

          matchEnd5("2")

        else

          case8()

      };

      case8(){

        matchEnd5("3")

      };

      matchEnd5(x: String){

        x

      }

    };




따라서 성능에 민감한 코드가 들어 있다면, 최대한 case 문의 앞쪽에 배치시켜야 한다. 


접근 빈도가 가장 높은 데, 패턴 매칭의 case 문 맨 끝에 있다면 선형 시간의 worst 시간에 걸려, 최악의 성능을 낼 수 있다. 


Posted by '김용환'
,


스칼라의 List는 Immutable이기 때문에 List에 뭔가를 추가하는 것은 문제가 된다. 


val l = List[Member]()
l += new Member
println(l.size)

이 코드는 실행 시 에러가 발생한다. 그 이유는 불변성을 중요하는 콜렉션 철학이 있다. 


Error:(25, 5) value += is not a member of List[Member]

  l += new Member




full qualied class name을 보려면 다음 코드를 사용한다.

println(List.getClass.getName)

결과는 scala.collection.immutable.List$이다. 








자바처럼 변성을 가진 List를 구현하고 싶다면, 이름이 조금 긴 mutable.MutableList를 사용한다. 

val list = mutable.MutableList[Member]()
list += new Member
list += new SchoolMember
println(list.size)


결과는 2이고, 컴파일/런타임의 에러는 발생하지 않는다. 







Posted by '김용환'
,

* 제네릭과 폴리모픽 메소드


스칼라의 generic(제네릭) 클래스와 자바와 비슷하다.



class Stack[T] {

  var elems: List[T] = Nil

  def push(x: T) { elems = x :: elems }

  def pop() { elems = elems.tail }

}



메소드에서 제네릭이 쓰이면 polymorphic methods라 한다.


def print[T](t: T): Unit = {
println(t)
}




* 타입 매개변수


스칼라에서 타입 바운드(type bounds)는 타입 매개변수와 타입 변수에 제약을 거는 행위이다. 이를 통해 타입에 안전하게(type safety) 코딩을 할 수 있도록 한다. 아래와 같은 3개의 타입 바운드가 존재한다. 


- Upper Bound (자바에서는 extends)

- Lower Bound (자바에서는 super)

- Context Bound

  이전에 View Bound( <%) : scala 2.10부터 deprecated되었고 Context Bound로 전환되었다. 



먼저  Upper Bound (한국 말로 상위 타입 경계라 한다)

[T <: S ] 이렇게 표현할 수 있다. T는 타입 매개변수이고 S는 타입이다.



예시를 살펴보자. 

class Member
class SchoolMember extends Member
class ClassMember extends SchoolMember

object Main extends App {

def print[T <: SchoolMember](t: T): Unit = {
println(t)
}

val member = new Member
val schoolMember = new SchoolMember
val classMember = new ClassMember

print(schoolMember)
print(classMember)

}


print메소드를 살펴보면, upper bound(<:)를 사용했다 .

print[T <: SchoolMember](t: T):


따라서 SchoolMember의 자식 클래스를 사용할 수 있도록 제한을 걸었다.

결과는 다음과 같다.


com.google.SchoolMember@3a03464

com.google.ClassMember@2d3fcdbd



upper bound(<:)의 제약을 넘어서면, 컴파일 타임 때는 발생하지 않지만,

타입 파라미터 바운드와 타입이 안 맞는다는 에러가 발생한다. 



//print(member)
// Error :
// Error:(23, 3) inferred type arguments [com.google.Member] // do not conform to method print's // type parameter bounds [T <: com.google.SchoolMember]

// Error:(23, 9) type mismatch;
// found : Member
// required: T




다음은  Lower Bound이다 (한국 말로 하위 타입 경계라 한다)

자바의 super 개념과 동일하다. 



간단한 예시를 살펴본다. 

object Main extends App {

class LowerBounds[Parent] {
def print[T >: Parent](t: T) {
println(t)
}
}

class Parent
class Child extends Parent

val parent = new Parent
val child = new Child

val instance = new LowerBounds[Parent]
instance.print(parent)

instance.print(child)
}

Parent, Parent보다 큰 타입만 받도록 하는 예제이다. LowerBounds 클래스에 [T >: Parent] 라는 제약이 있는 print 메소드를 정의했다. 


결과를 실행해본다.


Upper Bounds의 예시처럼 에러를 기대하겠지만, 실제로 테스트해보면 잘 출력된다. 단순한 Lower Bounds만으로는 스칼라가 에러를 출력하지 않는다. (에러가 안나는 원인에 대해서는 좀 더 공부해야 봐야겠다. )


com.google.Main$Parent@3a03464

com.google.Main$Child@2d3fcdbd






에러를 발생시키려면, implict으로 상속 구조를 표현해야 한다. 


아래와 같이 LowerBounds 클래스를 재정의한다.

class LowerBounds[Parent] {
def print[Y](y: Y)(implicit ev: Parent <:< Y): Unit = {
println(y)
}
}

그러면, 아래 코드에서 에러가 발생한다.


instance.print(child)


에러 내용은 다음과 같다. 


Error:(22, 17) Cannot prove that com.google.Main.Parent <:< com.google.Main.Child.

  instance.print(child)



현재 Option의 많은 메소드가 바로 Lower bounds 의미를 가지고 있다. orNull 메소드를 보면, 이전 예시와 동일한 형태임을 알 수 있다.  


@inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1 
@inline final def getOrElse[B >: A](default: => B): B =



참고로, 좀 더 Lower Bounds를 살펴보려면, 프로그래밍 스칼라(오현석 역)에 Option을 가지고 설명이 아주 잘되어 있으니, 참고하기 바란다.









다음은 Context Bound이다.(한국 말로 문맥 바운드라 한다)


이를 타입 클래스 패턴이라고 하며, 아래 링크의 implicit class 부분을 참조한다.

http://knight76.tistory.com/entry/scala-class-%EC%98%88%EC%8B%9C-2-%EC%95%94%EC%8B%9C%EC%A0%81-%EB%B3%80%ED%99%98implicit-conversion



간단히 보려면, 아래 링크를 클릭한다.

https://github.com/FlyScala/ProgrammingScala/wiki/5.4-%ED%83%80%EC%9E%85-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%8C%A8%ED%84%B4








이제는 Type Variance(타입 변성)에 대한 설명이다.


간단히 설명하면 T가 S의 하위 타입이면, Stack[T]는 Stack[S]의 하위 타입이라는 말이다. 


좋은 예시가 있어서 내용을 조금 이해하기 쉽게 만들었다.

참조 : https://coderwall.com/p/dlqvnq/simple-example-for-scala-covariance-contravariance-and-invariance



타입 매개변수마다 클래스를 생성한다.


+T는 Covariant(공변성), 

-T는 Contravariant(역변성),

T는 Invariant(불변성)




class Animal {}
class Mammal extends Animal{}
class Dog extends Mammal{}

class Covariant[+T]
class Contravariant[-T]
class Invariant[T]

object Main extends App {
def method1(box:Covariant[Mammal]) = println(box)
//method1(new Box1[Animal]) //type mismatch compile error
method1(new Covariant[Mammal])
method1(new Covariant[Dog])
method1(new Covariant[Nothing])

def method2(box:Contravariant[Mammal]) = println(box)
method2(new Contravariant[AnyRef])
method2(new Contravariant[Animal])
method2(new Contravariant[Mammal])
// method2(new Contravariant[Dog]) //type mismatch compile error
// method2(new Contravariant[Nothing]) //type mismatch compile error

def method3(box:Invariant[Mammal]) = println(box)
//method3(new Invariant[Animal]) //type mismatch compile error
method3(new Invariant[Mammal])
//method3(new Invariant[Dog]) //type mismatch compile error
//method3(new Invariant[Nothing]) //type mismatch compile error
}


[method1 설명]

Mammal의 공변성은 Mammal과 Mammal의 하위 타입을 포함한다. Nothing도 된다.


[method2 설명]

Mammal의 역변성은 Mammal과 Mammal의 상위 타입을 포함한다. AnyRef도 된다.


[method2 설명]

Mammal의 불변성은 Mammal만 된다. 





Posted by '김용환'
,

[scala] first class

scala 2016. 9. 26. 10:51



스칼라에서 first class는 1급 객체, 1급 함수에서 사용되는 prefix인데, 공부차 살펴보니..


개념 상으로는 언어의 first class를 가장 설명한 웹 페이지는 다음 블로그에 잘 설명되어 있다. 

http://changsuk.me/?p=1916




Programming in Scala 한국어판(마틴 오더스키,렉스 스푼,빌 베너스 공저/오현석,이동욱,반영록 공역)에 first class에 대한 주석이 그나마 전체적인 문맥을 가장 설명을 잘 되어 있다. 


first class을 언어에서 제약 없이 다룰 수 있는 대상이란 뜻으로,  ‘일반적인 값하고 큰 차이 없이 자유자재로 다룰 수 있는 값’ 정도로 이해해도 좋다.





Posted by '김용환'
,