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


스칼라의 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 '김용환'
,




스칼라에서는 Any 객체가 상위에 있고, AnyRef와 AnyVal로 나눠진다. 그리고, Nothing과 Null의 세계가 나눠져 있다. 
AnyVal은 주로 프리미티브 타입 쪽으로 관련되어 있다. 이는 값 클래스(Value Class)로 되어 스칼라 컴파일러가 최적화할 수 있는 기법으로 사용된다. 








일반 클래스는 기본적으로 AnyRef를 상속받는다. 사실 extends가 있으나 없으나 동일하다. 자바의 Object와 동일하기 때문이다. 



간단한 케이스 클래스 예시로 이해해보자. 

case class Man(name: String) extends AnyRef {
def canEqual(that: Any): Boolean = that.isInstanceOf[Man]

override def equals(obj: scala.Any): Boolean = super.equals(obj)

override def hashCode(): Int = super.hashCode()
}

object Main extends App {
var i: AnyRef = new AnyRef
println(i)

val samuel = Man("Samuel")
val matt = Man("Matt")
println(samuel.equals(matt))
println(samuel.canEqual(matt))
println(samuel.hashCode)
}



AnyRef 객체를 출력하면, Object가 출력된다. Man 클래스는 AnyRef에 있는 메소드 일부를 override해서 사용했다.

결과는 다음과 같다.


java.lang.Object@3a03464

false

true

759156157







이제 커링을 살펴본다.


커링은 타입 추론을 지원하는 스칼라의 함수 기능이다. 


여러 개의 매개변수가 가지고 있는 함수를 

매개변수가 하나인 함수로 나눌 수 있다. 


f (a, b) = c라는 함수 f가 있다고 가정하자.


F (a) = G,  G(b) = c 가 되는 함수로 만들 수 있다. 이 때 함수 F를 f의 커리라 한다. 

함수의 부분집합 같은 개념이라 할 수 있다.

예시를 살펴본다.


Numbers의 add 메소드는 일반적인 메소드이다. 이를 커링으로 만들어봤다. 
object Numbers {
def add(a: Int, b: Int) : Int = a + b

def addCurrying1(a: Int)(b: Int) : Int = a + b

def addCurrying2(a: Int) : Int => Int = b => a + b

def addCurrying3(a: Int) = (b: Int) => a + b
}

테스트 코드를 실행해 본다.

import com.google.Numbers._

println(add(1, 2))
println(addCurrying1(1)(2))
println(addCurrying2(1)(2))
println(addCurrying3(1)(2))

결과는 모두 3이다. 


커링을 잘 이해하기 위해서 좀 더 테스트를 진행한다. 커링 함수의 두 번째 매개변수 에 대한 위치 표시자로서, 매개변수를 받아 기존 값에 더하는 함수에 대한 참조 함수를 출력한다. 
println(addCurrying1 _)
println(addCurrying1(1) _)
결과는 다음과 같다.
<function1>
<function1>


하지만, addCurring2와 addCurring3 함수는 ()() 형태가 아니고 () 형태라서 런타임 에러가 발생한다. 

println(addCurrying2 _) // <function1> 출력
//println(addCurrying2(1) _)
//Error:(31, 23) _ must follow method; cannot follow Int => Int


println(addCurrying3 _) // <function1> 출력
//println(addCurrying3(1) _)
//Error:(31, 23) _ must follow method; cannot follow Int => Int


이번에는 부분함수처럼 사용해본다. addCurrying1 메소드는 ()() 형태 이기 때문에 부분함수로 받으려면 에러가 발생한다.
//val f1 = addCurrying1(1)
// Error:(27, 24) missing argument list for method addCurrying1 in object Numbers
//Unapplied methods are only converted to functions when a function type is expected.
//You can make this conversion explicit by writing `addCurrying1 _` // or `addCurrying1(_)(_)` instead of `addCurrying1`.
하지만, addCurrying2, addCurring3는 부분함수처럼 쓸 수 있다. 


val f2 = addCurrying2(1)
val f3 = addCurrying3(1)

val g2 = f2(2)
val g3 = f3(2)

assert(g2 == 3)
assert(g3 == 3)



커링을 언제 쓰나 봤더니, foldRight, reduceLeft, reduceRight, foldLeft 메소드에서 커링 함수를 사용하고 있다. 


override /*IterableLike*/
def foldRight[B](z: B)(@deprecatedName('f) op: (A, B) => B): B =
if (this.isEmpty) z
else op(head, tail.foldRight(z)(op))

override /*TraversableLike*/
def reduceLeft[B >: A](f: (B, A) => B): B =
if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft")
else tail.foldLeft[B](head)(f)

override /*IterableLike*/
def reduceRight[B >: A](op: (A, B) => B): B =
if (isEmpty) throw new UnsupportedOperationException("Nil.reduceRight")
else if (tail.isEmpty) head
else op(head, tail.reduceRight(op))


또한 retry하는 대표적인 예제에도 커링이 들어간다. 


def retry[T](n: Int)(fn: => T): T = {
  try {
    fn
  } catch {
    case e if n > 1 =>
      retry(n - 1)(fn)
  }
}







특화(specialization)은 제네릭 사용시 type erasure로 인해 boxing, unboxing이 발생할 수 있다. 예를 들어, fill 메소드에 엄청큰 수를 넣고 map 함수를 사용하면(에, List.fill(50000)(2).map(_* 3) ), 엄청난 boxing, unboxing이 일어나 성능이 저하될 수 있다. 이를 방지하기 위해서 나온 개념이다.


간단한 예시를 살펴본다. 


case class Count[T](value: T)

object Main extends App {
def newCount(l: Long): Count[Long] = Count(l)
}

별 것도 아닌 것처럼 보이지만, scalac -print로 확인해보면, 박싱을 진행하고 있다.




def newCount(l: Long): Count = new Count(scala.Long.box(l));




특화를 적용(@specialized 추가)하면 다음과 같다. 

case class Count[@specialized(Long, Int) T](value: T)

object Main extends App {
def newCount(l: Long): Count[Long] = Count(l)
}


scalac -print로 살펴보면, 클래스가 생겼고, boxing이 없는 코드로 바뀌어 있다. 



def newCount(l: Long): Count = new Count$mcJ$sp(l);



  <specialized> class Count$mcI$sp extends Count {

    <paramaccessor> <specialized> protected[this] val value$mcI$sp: Int = _;

    <stable> <accessor> <specialized> def value$mcI$sp(): Int = Count$mcI$sp.this.value$mcI$sp;

    override <stable> <accessor> <specialized> def value(): Int = Count$mcI$sp.this.value$mcI$sp();

    override <synthetic> <specialized> def copy$default$1(): Int = Count$mcI$sp.this.copy$default$1$mcI$sp();

    override <synthetic> <specialized> def copy$default$1$mcI$sp(): Int = Count$mcI$sp.this.value();

    def specInstance$(): Boolean = true;

    override <synthetic> <bridge> <specialized> <artifact> def copy$default$1(): Object = scala.Int.box(Count$mcI$sp.this.copy$default$1());

    override <stable> <bridge> <specialized> <artifact> def value(): Object = scala.Int.box(Count$mcI$sp.this.value());

    <specialized> def <init>(value$mcI$sp: Int): Count$mcI$sp = {

      Count$mcI$sp.this.value$mcI$sp = value$mcI$sp;

      Count$mcI$sp.super.<init>(null);

      ()

    }

  };












Posted by '김용환'
,



스칼라 클래스 이해하기 의 3편이다.


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

2편- [scala] class 예시 2 - 암시적 변환(implicit conversion - implicit converter/implicit class/implicit parameter)




믹스인(mixin) 클래스를 설명한다. 최근에 루비를 개발할 때 믹스인(mixin)을 보면서 엄청 방황(?)한 적이 있다. C++/java 개발에서 Ruby, Scala개발로 넘어가면서 재미있는 특징을 알게 된다. 

루비는 Include로 mixin을 구현한다. (자세한 내용은 http://blog.saltfactory.net/ruby/understanding-mixin-using-with-ruby.html을 참조한다).


Scala는 단일 상속을 지원하고, 다중 상속 같은 느낌을 믹스인으로 해결한다. 루비의 include  + 자바의 inline 개념으로 이해하면 될 것 같다.


Scala의 믹스인은 매우 간단하다. extends로 단일 상속으로 하게 하고, with로 믹스인을 구현한다. 

(처음에는 implements로 생각하는 바람에 다중 상속인 줄 알았다.)


abstract class Data {
def print1 = println("Data!!")
}

trait BigData {
def print2 = println("Big Data!!")
}

class InternalBigData extends Data with BigData {
}

object Main extends App {
val test = new InternalBigData
test.print1
test.print2
}


결과는 다음과 같이 간단하다.


Data!!

Big Data!!





이제, 내부 역어셈블링을 통해 내부 자바 코드를 살펴본다.



trait는 abstract class이며, 매개변수를 받지 않은 클래스이다. 

스칼라 컴파일러는 BigData trait를 interface로 바꾸고, 실제 구현 내용은 자식 클래스의 메소드로 inlining 시킨다. 




public abstract class Data

{


    public void print1()

    {

        Predef$.MODULE$.println("Data!!");

    }


    public Data()

    {

    }

}



public interface BigData

{


    public abstract void print2();

}




public class InternalBigData extends Data  implements BigData

{


    public void print2()

    {

        BigData.class.print2(this);

    }


    public InternalBigData()

    {

        BigData.class.$init$(this);

    }

}






다음은 AnyVal에 대한 설명이다. 


AnyVal 클래스는 모든 값 타입의 최상위 클래스이고, 내부 호스트 시스템에서 객체로 구현되지 않은 값을 생성한다.

AnyVal 클래스를 상속받은 클래스는 단일 매개변수가 하나의 public val인 주요 생성자가 단 하나가 있어야 하고, 해당 매개변수는 값 클래스가 될 수 없다. 또한 이것 외에도 제약이 좀 있다..




예시를 통해 공부해 본다. 간단한 테스트를 진행한다. 

case class Price(price: BigDecimal)
object Price {
def equals(a : Price, b : Price) : Boolean = {
a.price == b.price
}
}

object Main extends App {
val a = Price(2000)
val b = Price(2000)
println(Price.equals(a, b))
}

결과는 true이다. 


여기서 역어셈블링(jad)을 해서 내부 구조를 살펴본다.

Main 클래스를 살펴보면 다음과 같이 예상되는 코드가 생긴다. 



 public final void delayedEndpoint$Main$1()

    {

        a = new Price(BigDecimal$.MODULE$.int2bigDecimal(2000));

        b = new Price(BigDecimal$.MODULE$.int2bigDecimal(2000));

        Predef$.MODULE$.println(BoxesRunTime.boxToBoolean(Price$.MODULE$.equals(a(), b())));

    }


    public static final Main$ MODULE$ = this;

    private final Price a;

    private final Price b;

    private final long executionStart;

    private String scala$App$$_args[];

    private final ListBuffer scala$App$$initCode;



Price 클래스를 살펴본다.


public final class Price$

    implements Serializable

{


    public boolean equals(Price a, Price b)

    {

        BigDecimal bigdecimal;

        BigDecimal bigdecimal1 = a.price();

        bigdecimal1;

        bigdecimal1;

        bigdecimal = b.price();

        JVM INSTR ifnonnull 21;

           goto _L1 _L2

_L1:

        JVM INSTR pop ;

        if(bigdecimal == null) goto _L4; else goto _L3

_L2:

        bigdecimal;

        equals();

        JVM INSTR ifeq 32;

           goto _L4 _L3

_L4:

        true;

          goto _L5

_L3:

        false;

_L5:

        return;

    }




Price 같이 Wrapper class가 많아지면, 코드 관리는 편한데, 메모리를 많이 쓰고 GC에 영향을 줄 것이다. 스칼라 컴파일러에서 이런 메모리 압박을 줄이기 위해 2.10부터 AnyVal이라는 클래스를 만들었다. AnyVal 클래스는 값 클래스(value class)를 정의하고, 스칼라 컴파일러가 특별히 취급한다. 값 클래스는 인스턴 할당을 피하기 위해 컴파일 타임 때 최적화되고, 대신 래핑된 타입을 사용한다. 


이렇게 괜찮은 AnyVal을 상속받은 값 클래스는 제약이 당연히 있다. 

- 단일 매개변수가 하나의 public val인 주요 생성자가 단 하나가 있어야 한다. 

- 생성자의 매개변수는 값 클래스가 될 수 없다. 

- val 또는 var는 값 클래스 내부에서 사용할 수 없다.




위의 예시에서 class에 AnyVal 클래스를 상속한다.

case class Price(price: BigDecimal) extends AnyVal
object Price {
def equals(a : Price, b : Price) : Boolean = {
a.price == b.price
}
}

object Main extends App {
val a = Price(2000)
val b = Price(2000)
println(Price.equals(a, b))
}



Main 클래스를 역어셈블해보면, 다음과 같다. 



    public final void delayedEndpoint$Main$1()

    {

        a = BigDecimal$.MODULE$.int2bigDecimal(2000);

        b = BigDecimal$.MODULE$.int2bigDecimal(2000);

        Predef$.MODULE$.println(BoxesRunTime.boxToBoolean(Price$.MODULE$.equals(a(), b())));

    }


    private Main$()

    {

        scala.App.class.$init$(this);

        delayedInit(new Main.delayedInit.body(this));

    }


    public static final Main$ MODULE$ = this;

    private final BigDecimal a;

    private final BigDecimal b;

    private final long executionStart;

    private String scala$App$$_args[];

    private final ListBuffer scala$App$$initCode;



그리고, Price 클래스를 역어셈블링하면 다음과 같다.



public final class Price$

    implements Serializable

{


    public boolean equals(BigDecimal a, BigDecimal b)

    {

        a;

        BigDecimal bigdecimal = b;

        if(a != null) goto _L2; else goto _L1

_L1:

        JVM INSTR pop ;

        if(bigdecimal == null) goto _L4; else goto _L3

_L2:

        bigdecimal;

        equals();

        JVM INSTR ifeq 26;

           goto _L4 _L3

_L4:

        true;

          goto _L5

_L3:

        false;

_L5:

        return;

    }



스칼라가 컴파일러가 최적화를 이렇게 진행한다. 만약 Price의 타입이 만약 Int라면 어떻게 될까? 컴파일하면 자바 Primitivie type인 int로 변환된다. 이렇게 Primitive type이 존재하는 타입은 모두 변환되기 때문에 엄청난 성능 이득을 얻을 수 있다. 


public final class Price$

    implements Serializable

{


    public boolean equals(int a, int b)

    {

        return a == b;

    }





AnyVal이 너무 제약적이기 때문에, 유니버셜 트레이트로 좀 확장할 수 있다. 

유니버셜 트레이트는 Any를 상속한 트레이트이다.


역시 유니버셜 트레이트도 제약이 있다.

- 멤버는 def만 가지고 있으며, 초기화를 수행하지 않는다.

- 중첩 클래스 또는 객체 정의는 또한 불가능하다

- 다른 제한은 값 클래스가 유니버셜 트레이트(universal trait)말고 다른 것을 상속하지 않는다




trait Printable extends Any {
def print() : Unit = println(this)
}

case class Price(price: Int) extends AnyVal with Printable
object Price {
def equals(a : Price, b : Price) : Boolean = {
a.price == b.price
}
}

object Main extends App {
val a : Printable = Price(2000)
a.print()
}


스칼라 컴파일러가 문제 없이 잘 만들어줬다. 


public final class Price$

    implements Serializable

{


    public boolean equals(int a, int b)

    {

        return a == b;

    }




참고로 유니버셜 트레이트를 만드는 제약을 깨뜨리면, (다음과 같은 코드)

trait Printable extends Any {
val a = 1
def print() : Unit = println(this)
}


아래와 같은 컴파일러 에러가 발생한다. 

Error:(6, 7) field definition is not allowed in universal trait extending from class Any

  val a = 1



유니버설 트레이트가 스칼라에서 여기 저기 쓰인다. 대표적으로 Ordered trait가 있다.


trait Ordered[A] extends Any with java.lang.Comparable[A] {





다음은 type 키워드에 대한 설명이다. abstract type이라 지칭한다.

 type 키워드는 trait와 class에서 별명(alias)로 사용할 수 있다. trait에서 정한 타입을 추상화한다. Generic과 많이 유사하지만, 나름 영역이 있다고 하니. 많이 써보고 generic과 abstract type의 용도를 봐야 할 것 같다. 


trait Price {
type T
def method : T
}

class CarPrice extends Price {
type T = Int
def method: T = 1000
}

object Main extends App {
println((new CarPrice).method)
}

결과는 1000이다. 












Posted by '김용환'
,


scala 코드가 어떻게 jvm에서 인식될 수 있는 지 확인하려면 세 가지 방법이 있다.


1. jad

2. javap 

3. scalac -print




1.jad

jad는 scalac  자바 class 화일을 java로 역어셈블링한다. 간단한 파일은 scalac -print로 테스트해볼 수 있지만, scalac 파일이 많아지면 너무 많아진다. 필요한 것만 보기에 좋다.


$ scalac Main.java

$ jad *.class

$ls *

Main$.class                 Main.class                  Price$.class                Price.jad                   Printable.jad

Main$.jad                   Main.jad                    Price$.jad                  Printable$class.class

Main$delayedInit$body.class Main.scala                  Price.class                 Printable.class


클래스 파일을 에디터로 다음처럼 살펴보면 된다.


$ vi Price.jad


// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.

// Jad home page: http://www.kpdus.com/jad.html

// Decompiler options: packimports(3)

// Source File Name:   Main.scala


import scala.*;

import scala.collection.Iterator;


public final class Price

    implements Printable, Product, Serializable

{


    public static String toString$extension(int i)

    {

        return Price$.MODULE$.toString$extension(i);
...





2. javap

JDK 툴을 사용해서 jvm languague를 살펴본다. 


$ scalac Main.java

$ javap -c Price

(만약 private 메소드를 보려면, -private을 추가한다. $ javap -c -private Price)



Compiled from "Main.scala"

public final class Price implements Printable,scala.Product,scala.Serializable {

  public static java.lang.String toString$extension(int);

    Code:

       0: getstatic     #22                 // Field Price$.MODULE$:LPrice$;

       3: iload_0

       4: invokevirtual #24                 // Method Price$.toString$extension:(I)Ljava/lang/String;

       7: areturn





3. scalac -print

java 기반이 아닌 scala식 역어셈블링이다.


관련된 코드가 예쁘게 모두 나온다. 너무 많은 클래스를 컴파일 할 때는 너무 많이 나올 수 있다. 


$ scalac -print Main.scala

[[syntax trees at end of                   cleanup]] // Main.scala

package <empty> {

  abstract trait Printable extends Object {

    def print(): Unit

  };

  final case class Price extends Object with Printable with Product with Serializable {

    def print(): Unit = Printable$class.print(Price.this);

    <caseaccessor> <paramaccessor> private[this] val price: Int = _;

    <stable> <caseaccessor> <accessor> <paramaccessor> def price(): Int = Price.this.price;

    <synthetic> def copy(price: Int): Int = Price.copy$extension(Price.this.price(), price);

    <synthetic> def copy$default$1(): Int = Price.copy$default$1$extension(Price.this.price());

    override <synthetic> def productPrefix(): String = Price.productPrefix$extension(Price.this.price());

    <synthetic> def productArity(): Int = Price.productArity$extension(Price.this.price());

    <synthetic> def productElement(x$1: Int): Object = Price.productElement$extension(Price.this.price(), x$1);

    override <synthetic> def productIterator(): Iterator = Price.productIterator$extension(Price.this.price());

    <synthetic> def canEqual(x$1: Object): Boolean = Price.canEqual$extension(Price.this.price(), x$1);

    override <synthetic> def hashCode(): Int = Price.hashCode$extension(Price.this.price());

    override <synthetic> def equals(x$1: Object): Boolean = Price.equals$extension(Price.this.price(), x$1);

    override <synthetic> def toString(): String = Price.toString$extension(Price.this.price());

    def <init>(price: Int): Price = {

      Price.this.price = price;

      Price.super.<init>();

      Printable$class./*Printable$class*/$init$(Price.this);

      scala.Product$class./*Product$class*/$init$(Pr

Posted by '김용환'
,



<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 '김용환'
,


스칼라 클래스를 공부하고 있다.

자바 클래스와 비슷한 Member 클래스를 스칼라로 만들었다. 


class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

클래스 필드의 modifier는 private로 선언해야 외부에서 사용할 수 없다.(modifier를 쓰지 않으면 public이다, 참고로 protected는 자바처럼 그대로 존재한다.)

상위 클래스에서 이미 선언된 toString()을 정의할 때는 override를 써야 컴파일 에러가 발생하지 않는다.




사용하는 방법은 다음과 같다. 인스턴스를 생성하고 Member를 얻는다.


val member : Member = new Member(1, "samuel")
println(member.get(1))
println(member)

결과는 다음과 같다. 


samuel

(1, samuel)



public 필드를 가진 Dummy 클래스를 사용해보자.

class Dummy() {
var x = ""
}


Dummy 클래스를 다음처럼 사용할 수 있다.

val dummy = new Dummy()
dummy.x = "1"
println(dummy.x)





싱글톤 객체(Singleton object)를 생성해본다. object Member를 하나 추가해본다.


class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

object Member {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}
}



사용할 때는 static method처럼 사용할 수 있다. 

object Member 를 class Member의 동반 객체(companion object)라 부른다. 반대로 class Member를 object Member의 동반 클래스(companion class)라 부른다.

val member : Member = new Member(1, "samuel")
Member.print(member)



결과는 다음과 같다.


id : 1, name : samuel



위의 코드는 일반 생성 코드와 싱글톤 객체의 메소드가 있으니 좀 보기 이상할 수 있다.

아예 싱글톤 객체로만 사용해본다.

object Member에 apply 메소드를 추가하여 new 없이 생성자 역할을 하도록 한다. 



class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

sealed trait Print {
def print(_member : Member) : Unit
}

object Member extends Print {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}

def apply(_id: Int, _name: String) : Member = {
new Member(_id, _name)
}
}



new 생성자 없이 Member 객체를 생성했다. 

Member.print(Member(1, "samuel"))
Member.print(Member(2, "jackson"))

결과는 다음과 같다.


id : 1, name : samuel

id : 2, name : jackson





자세한 내용은 아래 링크를 참조한다.

http://knight76.tistory.com/entry/scala-%EC%8B%B1%EA%B8%80%ED%86%A4-%EA%B0%9D%EC%B2%B4-%EB%8F%85%EB%A6%BD-%EA%B0%9D%EC%B2%B4-%EB%8F%99%EB%B0%98-%ED%81%B4%EB%9E%98%EC%8A%A4




이번에는 unapply 메소드를 소개한다. unapply 메소드는 apply 메소드의 반대되는 개념이다. 

스칼라에서는 apply 메소드를 injection이라 부르고, unapply를 extraction이라 부른다. unapply를 통해 객체를 얻었다면, extractor object라 부른다.



다음 예제를 살펴본다.  unapply를 추가했고, 리턴 값을 튜플로 했다. 


 

class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

sealed trait Print {
def print(_member : Member) : Unit
}

object Member extends Print {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}

def apply(_id: Int, _name: String) : Member = {
new Member(_id, _name)
}

def unapply(member: Member) : Option[(Int, String)] = {
if (member.id > 0 && !member.name.isEmpty)
Some(member.id, member.name)
else
None
}
}

object Main extends App {
println(Member.unapply(new Member(1, "samuel")))
}


결과는 다음과 같다.


Some((1,samuel))









에는 case class를 생성한다. case class는 생성자 매개변수를 노출하고 패턴 매칭을 가능케 한다. equals와 toString 메소드를 지원한다.


예시로 Member 추상 클래스와 하위 case class를 생성한다.

abstract class Member

case class CompanyMember(name: String) extends Member {}
case class SchoolMember(name: String) extends Member {}
case class CommunityMember(name: String) extends Member {}


case class를 테스트해본다.

val member = CompanyMember("samuel")
println(member) // toString
println(member == CompanyMember("samuel")) //equals

결과는 다음과 같다.


CompanyMember(samuel)

true




이제 패턴 매칭을 해본다. printMember 메소드 하나를 만들고, case-match 문을 간단히 추가하다.


def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

실행 코드는 다음과 같다.


val list = List(CompanyMember("samuel"), CommunityMember("matt"), 
                SchoolMember("jeff"))
list.foreach { printMember }

결과는 다음과 같다.


Company Member

Community Member

School Member



case class가 abstract class Member를 상속받았지만, 이를 trait로 바꿔도 동작한다.


sealed trait Member

case class CompanyMember(name: String) extends Member {}
case class SchoolMember(name: String) extends Member {}
case class CommunityMember(name: String) extends Member {}








지금까지, 실행 코드는 지금까지 object Main에 def main(args: Array[String]): Unit을 사용했다.


object Main {
def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

def main(args: Array[String]): Unit = {
val list = List(CompanyMember("samuel"), CommunityMember("matt"), SchoolMember("jeff"))
list.foreach { printMember }
}
}


object Main이 App 트레이트를 상속하면, 조금 단순해 질 수 있다.



object Main extends App {
def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

val list = List(CompanyMember("samuel"), CommunityMember("matt"), SchoolMember("jeff"))
list.foreach { printMember }
}


내부 구현은 아래 링크를 참조한다.

https://charsyam.wordpress.com/2015/02/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-scala-%EC%9D%98-app-trait%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94%EA%B0%80/



나는 여기서 디컴파일된 부분을 살펴본다.

object Main extends App {
println("App")
}


Main$.class 파일에 해당 내용이 담겨져 있다. 


public final class Main$

    implements App

{


    public long executionStart()

    {

        return executionStart;

    }


    public String[] scala$App$$_args()

    {

        return scala$App$$_args;

    }


    public void scala$App$$_args_$eq(String x$1[])

    {

        scala$App$$_args = x$1;

    }


    public ListBuffer scala$App$$initCode()

    {

        return scala$App$$initCode;

    }


    public void scala$App$_setter_$executionStart_$eq(long x$1)

    {

        executionStart = x$1;

    }


    public void scala$App$_setter_$scala$App$$initCode_$eq(ListBuffer x$1)

    {

        scala$App$$initCode = x$1;

    }


    public String[] args()

    {

        return scala.App.class.args(this);

    }


    /**

     * @deprecated Method delayedInit is deprecated

     */


    public void delayedInit(Function0 body)

    {

        scala.App.class.delayedInit(this, body);

    }


    public void main(String args[])

    {

        scala.App.class.main(this, args);

    }


    public final void delayedEndpoint$com$google$Main$1()

    {

        Predef$.MODULE$.println("App");

    }


    private Main$()

    {

        scala.App.class.$init$(this);

        delayedInit(new Main.delayedInit.body(this));

    }


    public static final Main$ MODULE$ = this;

    private final long executionStart;

    private String scala$App$$_args[];

    private final ListBuffer scala$App$$initCode;


    static

    {

        new Main$();

    }

}




이제, 간단한 보조 생성자를 살펴본다. def this를 사용할 수 있다.


class Test(n: Int, m: Int) {
def this(n: Int) = this(n, 0)
}

object Main extends App {
new Test(1, 0)
new Test(1)
}


전제 조건을 확인할 수 있는 require가 있다.



class Test(n: Int, m: Int) {
require(n > 0)
def this(n: Int) = this(n, 0)
}

object Main extends App {
new Test(1, 0)
new Test(1)
new Test(-1)
}


다음처럼 IllegalArgumentException이 발생한다. 


Caused by: java.lang.IllegalArgumentException: requirement failed

at scala.Predef$.require(Predef.scala:212)



참고로 동일한 기능으로 assert도 있다.


assert(n > 0)

다음처럼 AssertionError가 발생한다.


Exception in thread "main" java.lang.AssertionError: assertion failed



require는 생성자 매개변수의 검증을 위해, assert는 내부 로직의 검증을 위해 사용하는 것이 좋은 것 같다.






다음은 합성 타입(compound type)이다. 두 trait를 합쳐진 합성된(compound, 교집합) 채로 사용한다. 이를 통해 강력한 타입 시스템을 가지면서 유연성을 가질 수 있다. 


아래 예시는 joinAndFork 메소드에 적용할 수 있는 메소드는 Joinable과 Forkable을 상속한 클래스라고 명시적으로 규정할 수 있다. 


trait Joinable {
def join: String = { "join" }
}

trait Forkable {
def fork: String = { "fork" }
}

class JoFo extends Joinable with Forkable {
override def join: String = super.join
override def fork: String = super.fork
}

object Main extends App {

def joinAndFork(obj: Joinable with Forkable): Joinable = {
println(obj.join)
println(obj.fork)
obj
}
joinAndFork(new JoFo)
}

결과는 다음과 같다.


join

fork






apply 메소드와 unapply 메소드에서 사용했던 Member 클래스를 제네릭 클래스로 만들어본다.


class Member[T](_id: Int, _name: T) {
private var id = _id
private var name : T = _name

def get(_id: Int): T = {
name
}

def set(_id: Int, _name: T) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

실행을 간단히 해본다.

val member = new Member(1, "aa")
println(member.get(1))

결과는 aa가 출력된다. 





다음은 묵시적 변환, 묵시적 클래스, 묵시적 매개변수 공부한 내용이다. 


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


Posted by '김용환'
,

[scala] List 예시 - 2

scala 2016. 9. 19. 18:34


List의 메소드에 대한 설명이다. 

head 메소드는 리스트의 첫 번째 원소를 리턴하지만, tail 메소드는 head 메소드에서 리턴하는 값을 제외한 나머지를 리턴한다(나는 언제나 이 부분이 어색하다). isEmpty 메소드는 List가 비어 있는지 확인한다. 


val list = List(1,2,3)
println(list.head)
println(list.tail)
println(list.isEmpty)


결과는 다음과 같다.


1

List(2, 3)

false








리스트 소스를 보면, 리스트의 멤버 변수는 다음과 같다. 


def isEmpty: Boolean
def head: A
def tail: List[A]





빈 리스트에 isEmpty 메소드를 호출하고 head 메소드를 호출해본다. 

val list = List()
println(list.isEmpty)
println(list.head)

결과는 다음과 같다. head에 기본적으로 Nil이 저장되기때문에 관련 에러가 발생한다.


true

Exception in thread "main" java.util.NoSuchElementException: head of empty list

at scala.collection.immutable.Nil$.head(List.scala:420)

at scala.collection.immutable.Nil$.head(List.scala:417)



Nil을 할당하면, head메소드를 호출할 수 있다. 

val list2 = Nil
println(list2.head)


결과는 다음과 같다. 

Exception in thread "main" java.util.NoSuchElementException: head of empty list

at scala.collection.immutable.Nil$.head(List.scala:420)




Nil은 왜 List일까 생각할 수 있겠지만, Nil은 List[Nothing]을 상속받은 객체이다. 즉, 리스트  케이스 타입 중 하나이다. 


case object Nil extends List[Nothing] {


리스트 객체에 tail을 계속 붙일 수 있다.
val list = List(1,2,3)
println(list.tail.tail)
println(list.tail.tail.tail)

결과는 다음과 같다.

List(3)
List()



tail을 엘리먼트 개수 대비해서 많이 호출하면 에러가 발생할 수 있다. 


println(list.tail.tail.tail.tail)

결과는 다음과 같은 Exception이 발생된다. 



Exception in thread "main" java.lang.UnsupportedOperationException: tail of empty list

at scala.collection.immutable.Nil$.tail(List.scala:422)

at scala.collection.immutable.Nil$.tail(List.scala:417)




head, tail과 비슷한 last와 init이 있다. 


val list = List(1,2,3)
println(list.init)
println(list.last)


결과는 다음과 같다.


List(1, 2)

3




리스트의 length 메소드는 리스트의 길이를 리턴한다.

val list = List(1,2,3)
println(list.length)


결과는 다음과 같다.


3



리스트의 reverse 메소드이다. 

val list = List(1,2,3)
println(list.reverse)

결과는 다음과 같다.


List(3, 2, 1)




리스트의 drop 메소드이다. drop(1)은 처음부터 첫 번째 엘리먼트까지 버리고 나머지만 얻고, drop(2)는 처음부터 두 번째 엘리먼트까지 버리고 나머지를 버린다는 의미이다.

val list = List(1,2,3)
println(list.drop(1))
println(list.drop(2))

결과는 다음과 같다. 


List(2, 3)

List(3)



리스트의 splitAt 메소드는 다음과 같다. 


val list = List(1,2,3)
println(list.splitAt(1))
println(list.splitAt(2))

결과는 다음과 같다.


(List(1),List(2, 3))

(List(1, 2),List(3))



스칼라는 각 엘리먼트 요소에 index로 접근할 수 있다. 

val list = List(1,2,3)
println(list(0))
println(list(1))
println(list(2))
println()
println(list.apply(0))
println(list.apply(1))
println(list.apply(2))

결과는 다음과 같다.


1

2

3


1

2

3



만약 index를 잘못 넣어 가르키는 index에 엘리먼트가 없다면 IndexOutOfBoundsException이 발생한다. 


Exception in thread "main" java.lang.IndexOutOfBoundsException: 3
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:65)
at scala.collection.immutable.List.apply(List.scala:84)


List에 index를 두든, apply(index)를 두든 아래 소스를 찾도록 되어 있다. 소스를 찾아보면 다음 구현으로 되어 있다.
먼저 drop으로 list를 얻은 후, 비어있지 않는다면 가장 맨 앞의 엘리먼트를 리턴하도록 되어 있다. 
def apply(n: Int): A = {
val rest = drop(n)
if (n < 0 || rest.isEmpty) throw new IndexOutOfBoundsException("" + n)
rest.head
}

flatten과 zip은 다음에서 설명했다.
http://knight76.tistory.com/entry/scala-map-flatten-flatmap-%EC%98%88%EC%8B%9C

zip와 unzip은 다음에서 설명했다.
http://knight76.tistory.com/entry/scala-zip-unzip-%EC%98%88%EC%8B%9C



Array의 mkString 메소드와 달리 List의 mkString은 비슷하다.
(http://knight76.tistory.com/entry/scala-Array-%EC%98%88%EC%8B%9C)

val list = List("s", "a", "m", "u", "e", "l")
println(list.mkString(":"))

결과는 다음과 같다.

s:a:m:u:e:l


리스트의 toArray는 배열로 변경한다. 
val list = List("s", "a", "m", "u", "e", "l")
println(list.toArray mkString " ")

결과는 다음과 같다.

s a m u e l




리스트의 copyToArray는 기존의 배열에 복사한다. 
val list = List("s", "a", "m", "u", "e", "l")
val emptyList = new Array[String](7)
list.copyToArray(emptyList, 0)
println(emptyList mkString " ")
결과는 다음과 같다. 초기화된 배열은 모두 null이기 때문에 맨 마지막 요소는 null로 표시되었다.

s a m u e l null



리스트의 map 예시이다. 
val list = List("s", "a", "m", "u", "e", "l")
println(list.map(_ + "X"))
println(list.flatMap(_ + "X"))

결과는 다음과 같다. 

List(sX, aX, mX, uX, eX, lX)
List(s, X, a, X, m, X, u, X, e, X, l, X)




List의 filter와 find 메소드의 예시이다. 
val list = List("s", "a", "m", "u", "e", "l")
println(list.filter(_ == "s"))
println(list.find(_ == "s"))

결과는 다음과 같다. filter의 결과 타입은 List이지만, find의 결과 타입은 Some이다. 

List(s)
Some(s)



만약 없는 요소를 filter, find하면 확연히 결과를 자세히 알 수 있다.

println(list.filter(_ == "z"))
println(list.find(_ == "z"))

결과는 다음과 같다.

List()
None



리스트의 partition은 리스트를 두 개로 나눈다.
val list = List(0,1,2,3,4,0)
println(list.partition(_ > 3))


결과는 다음과 같다.
(List(4),List(0, 1, 2, 3, 0))

partition메소드의 원형을 보면, predicate가 참이면, 두 개의 리스트를 리턴한다. 
def partition(p: A => Boolean): (Repr, Repr) = {
val l, r = newBuilder
for (x <- this) (if (p(x)) l else r) += x
(l.result, r.result)
}




리스트의 takeWhile은 좀 특이한 메소드이다. 함수를 받고, 조건식이 맞으면 ListBuffer에 넣었다가 조건식이 맞지 않을 때의 값을 리턴한다.  여기서 완전 주의할 것이 처음 엘리먼트부터 참이어야 한다는 점이다.!!!

@inline final override def takeWhile(p: A => Boolean): List[A] = {
val b = new ListBuffer[A]
var these = this
while (!these.isEmpty && p(these.head)) {
b += these.head
these = these.tail
}
b.toList
}

예시를 살펴본다. 
첫 번째, 4보다 작은 predicate을 주었다. 리스트의 처음 엘리먼트부터 참으로 가다가 엘리먼트의 값이 4에서 false가 되었다. (결과는 List(0,1,2,3) 일 것이다.)
두 번째, -1보다 큰 predicate을 주었다. 리스트의 처음 엘리먼트부터 참이다가, 엘리먼트가 -1인 부분에서 false가 되었다. (결국 결과는 List(0,1,2,3,4,5)가 될 것이다)
세 번째, 리스트의 처음 엘리먼트의 값은 3보다 크지 않다. 따라서 어떠한 List 엘리먼트도 추가되지 않는다. 결과는 List()가 된다.

val list = List(0,1,2,3,4,5,-1,0)
println(list.takeWhile(_ < 4))
println(list.takeWhile(_ > -1))
println(list.takeWhile(_ > 3))



예상대로 되었는지 결과를 살펴본다.


List(0, 1, 2, 3)

List(0, 1, 2, 3, 4, 5)

List()



다음은 takeWhile의 반대 메소드인 dropWhile이다. 결과를 살펴보면, 상반된 결과이지만, 구현이 조금 다르다.

dropWhile의 메소드 원형은 다음과 같다. 내부 메소드와 재귀 메소드를 적절히 사용했다.


@inline final override def dropWhile(p: A => Boolean): List[A] = {
@tailrec
def loop(xs: List[A]): List[A] =
if (xs.isEmpty || !p(xs.head)) xs
else loop(xs.tail)

loop(this)
}


dropWhile 예시는 다음과 같다. 

val list = List(0,1,2,3,4,5,-1,0)
println(list.dropWhile(_ < 4))
println(list.dropWhile(_ > -1))
println(list.dropWhile(_ > 3))

결과는 다음과 같다. 


List(4, 5, -1, 0)

List(-1, 0)

List(0, 1, 2, 3, 4, 5, -1, 0)




span 메소드는 takeWhile과 dropWhile 두 메소드를 하나도 합쳐 튜플로 리턴한다.


val list = List(0,1,2,3,4,5,-1,0)
println(list.span(_ < 4))

결과는 다음과 같다.


(List(0, 1, 2, 3),List(4, 5, -1, 0))



구현은 다음과 같다.

@inline final override def span(p: A => Boolean): (List[A], List[A]) = {
val b = new ListBuffer[A]
var these = this
while (!these.isEmpty && p(these.head)) {
b += these.head
these = these.tail
}
(b.toList, these)
}


리스트의 forall 메소드와 exists 메소드의 예시이다.

forall 메소드는 리스트의 모든 엘리먼트가 참일 때만 true를 리턴하고, exists 메소드는 하나라도 참이면 true를 리턴한다. 

val list = List(0,1,2,3,4,5,-1,0)
println(list.forall(_ > -100))
println(list.exists(_ == 0))


결과는 다음과 같다.


true

true




foldLeft와 foldRight는 우선 순위 연산을 지원하는 메소드이다.

val list = List(1,2,3)
println(list.foldLeft(1)(_ * _))
// ((1 * 1) * 2) * 3 = 6
println(list.foldLeft(2)(_ * _))
// ((2 * 1) * 2) * 3 = 12

println(list.foldRight(1)(_ * _))
// 1 * (2 * (3 * 1)) = 6
println(list.foldRight(2)(_ * _))
// 1 * (2 * (3 * 2)) = 12

결과는 다음과 같다.


6

12

6

12



리스트의 sortWith 메소드는 소팅을 지원한다. 다음 예시는 오른차순, 내림차순을 출력한다.

val list = List(1,2,3,-1,0)
println(list.sortWith(_ < _))
println(list.sortWith(_ > _))

결과는 다음과 같다.


List(-1, 0, 1, 2, 3)

List(3, 2, 1, 0, -1)




리스트의 팩토리 메소드이다.

http://www.scala-lang.org/docu/files/collections-api/collections_45.html




List.empty 예시이다.

println(List.empty)

결과는 List()이다.




List.range 예시이다.

println(List.range(1,5))
println(List.range(1,5,1))
println(List.range(1,6,2))


결과는 다음과 같다. 매개변수는 (start, end, step)을 의미한다. 

List(1, 2, 3, 4)

List(1, 2, 3, 4)

List(1, 3, 5)




List.concat 예시이다.

println(List.concat(List('s'), List('a'), List('m')))

결과는 다음과 같다.




List.fill 예시이다.

println(List.fill(2)("samuel"))

예시는 다음과 같다.



List.tabulate 예시이다.



println(List.tabulate(2)(a => a + 1))


결과는 다음과 같다. 


List(1, 2)




List.tabulate 메소드로 List(samuel, samuel)를 출력할 수도 있다. 


println(List.tabulate(2)(_ => "samuel"))






Posted by '김용환'
,




scala의 List 연산자 중, 

:::는 triple colon이라고 하고, 

::는 cons(콘즈)라고 한다.



Scala의 List는 단방향 연결 리스트이고 sealed abstract class이다. Nil과 ::라는 구현이 있다.


sealed trait List[+A] 

case object Nil extends List[Nothing] 

case class ::[A](head: A, tail: List[A]) extends List[A] 




List 트레이트 정의에 플러스(+) 기호가 있는 것을 주목한다. 플러스 기호는 List가 타입 매개변수 A에 공변성(covariant)이 있다는 것을 가르킨다. 공변성은 일반적인 타입에 다형성 제약을 표현할 수 있다. 예를 들면 다음 클래스를 살펴본다.


sealed trait Base

case class BaseImpl(value: Int) extends Base


여기에 Base와 Impl 간에 관계를 설명한다. Impl 클래스는 Base의 서브 타입이다. List가 함께 쓰인다면, 공변성은 List[Impl]은 List[Base]의 서브 타입이라는 뜻을 의미한다. 예시로 표현한 것은 공변성이 다음 코드를 컴파일할 수 있음을 의미한다. 


val bases : List[Base] = List[BaseImpl](BaseImpl(1))
println(bases)

결과는 다음과 같다.

List(BaseImpl(1))





이제 간단한 List 예시를 살펴본다. 적당하게 List를 선언한다. 

val list1 = List(1,2)
val list2 = List(3)


:: (콘즈)를 사용한다. 

val list3 = list1 :: list2
println(list3)



결과는 다음과 같다.리스트의 엘리먼트가 리스트가 되었다.


List(List(1, 2), 3)




이번에는 triple 리스트를 보자. 

val list4 = list1 ::: list2
println(list4)
val list4_1 = list2.:::(list1)
println(list4_1)


결과는 다음과 같다.

List(1, 2, 3)




::(콘즈)는 ::를 사용할 때와 .::()을 사용할 때가 정 반대임을 잘 기억하면 좋다.  ::(콘즈)는 두번째 리스트 끝에 첫번째 리스트를 붙이라는 의미를 가진다. 

val list5 = ::(1, ::(2, ::(3, Nil)))
println(list5)

val list5_1 = (1 :: (2 :: (3 :: Nil)))
println(list5_1)

val list5_2 = Nil.::(3).::(2).::(1)
println(list5_2)

val list5_3 = 1 :: 2 :: 3 :: Nil
println(list5_3)


결과는 모두 갖으며, 다음과 같다.

List(1, 2, 3)

List(1, 2, 3)

List(1, 2, 3)

List(1, 2, 3)





List는 다른 콜렉션과 다르게 패턴 매치 기능을 가지고 있다. ::(콘즈)와 함께 사용하면 재미있는 것을 만들어 볼 수 있다. List의 엘리먼트를 순차적으로 패턴매치를 하는 예시이다. 


0 엘리먼트부터 시작하는 경우에 대해서 다음 엘리먼트를 x에 저장하고 나머지를 rest로 저장하고 위해, 위에서 사용했던 ::을 사용해봤다. (0 :: x :: rest)

List(0, 1, 2, 3, 4, 5) match {
case 0 :: x :: rest => println(s"2번째: $x, 나머지: $rest")
case _ => println("others")
}

결과는 다음과 같다.


2번째: 1, 나머지: List(2, 3, 4, 5)


만약 아래 내용을 추가하지 않았다면, warning이 발생한다.


List(0, 1, 2, 3, 4, 5) match {
case 0 :: x :: rest => println(s"2번째: $x, 나머지: $rest")
}


scala에서는 List의 엘리먼트가 원치 않은 값에 대해서 처리할 수 없음을 warning으로 알려준다. 이런 이유로 case _ 구문을 추가했다.


Warning:(40, 9) match may not be exhaustive.

It would fail on the following inputs: List((x: Int forSome x not in 0)), List((x: Int forSome x not in 0), _), List(0), Nil

    List(0, 1, 2, 3, 4, 5) match {



정상적으로 출력되는지 확인하기 위해 다르게 테스트해본다.


List(6, 1, 2, 3, 4, 5, null) match {
case 0 :: x :: rest => println(s"2번째: $x, 나머지: $rest")
case _ => println("others")
}


결과는 다음과 같다. 


others




Scala의 List에 ::과 :::가 지원하는 것은 List에 prepend 연산이 최적화되어 있기 때문일지 모르겠다. 앞에 붙이는 연산은 싸고, 뒤로 붙이는 연산은 비싸다. 뒤에 붙이면 deep copy하고 새로운 리스트를 생성한다.


scala 문서(http://docs.scala-lang.org/overviews/collections/performance-characteristics.html)에 나온 이유가 이런 이유이다. append 연산은 constant time이지만, insert나 prepend는 linear하다. 




head
tailapplyupdateprependappendinsert
immutable       
ListCCLLCL-




CThe operation takes (fast) constant time.
LThe operation is linear, that is it takes time proportional to the collection size.







Posted by '김용환'
,

[scala] Array 예시

scala 2016. 9. 12. 18:53


scala의 Array 예제이다. 


val array1 = Array(1,2,3)
val array2 = Array(3,4,5)


+: 연산자를 사용하면 재미있는 결과를 볼 수 있다. 앞과 뒤에 쉽게 붙일 수 있다. c++ 연산자 오버로딩과 비슷하게 사용할 수 있다. 

println(-1 +: Array(1,2,3) :+ 4 mkString " ")

결과는 다음과 같다. 


-1 1 2 3 4



Array 객체의 내용을 출력하려면, mkString 메소드를 사용해야 엘리먼트를 볼 수 있다. 그냥 출력하면 내부에서 관리하는 객체 이름으로 보인다. 

++ 연산자는 Array 객체를 서로 합친다. 


//println(array1 + array2) //compile error
println(array1 ++ array2) //내부 객체 이름 출력
println(array1 ++ array2 mkString " ")


결과는 다음과 같다.


[I@3567135c

1 2 3 3 4 5



intersect와 union 예제이다. union 메소드 결과와 Array.concat 메소드 결과는 동일하다.


println(array1 intersect array2 mkString " ")
println(array1 union array2 mkString " ")
println(Array.concat(array1, array2) mkString " ")

결과는 다음과 같다.


3

1 2 3 3 4 5

1 2 3 3 4 5



두 배열의 엘리먼트를 합치면서 유일한 값만 뽑으려면 distinct 메소드를 사용한다.

A-B 집합연산에 해당하는 diff메소드를 사용할 수도 있다.

println((array1 union array2 distinct) mkString " ")
println(array1 diff array2 mkString " ")


결과는 다음과 같다.


1 2 3 4 5
1 2



배열의 엘리먼트의 순서를 거꾸로 하려면 reverse 메소드를 사용한다.


println((array1.reverse).mkString(" "))

결과는 다음과 같다.


3 2 1




range를 array로 변환할 수도 있다.

println((0 to 3).toArray.mkString(" "))

결과는 다음과 같다.

0 1 2 3





Array.fill 메소드는 c의 memset과 같은 느낌으로 초기화할 수 있는 메소드이다. 

Byte 타입으로 3개의 엘리먼트를 가진 Array를 0으로,

Byte 타입으로 5개의 엘리먼트를 가진 Array를 1로 초기화하려면 다음과 같이 호출할 수 있다.

println(Array.fill[Byte](3)(0) mkString " ")
println(Array.fill[Byte](5)(1) mkString " ")

결과는 다음과 같다.

0 0 0

1 1 1 1 1




만약 5개의 엘리먼트를 Int 타입으로 랜덤하게 생성하려면 다음을 호출한다.



println(Array.fill[Int](5){scala.util.Random.nextInt(5)} mkString " ")

랜덤 결과 값은 다음과 같다.


4 0 2 0 2






Array.fill 메소드로 2*2 배열을 초기화할 수 있다. 결과값을 보려면, mkString에 .deep를 추가한다. 

deep.mkString을 사용하니 내용을 출력할 수 있었다.

val array = Array.fill[Int](2,2){scala.util.Random.nextInt(5)}
println(array mkString " ")
println(array.deep.mkString(" "))


결과는 다음과 같다.


[I@73f792cf [I@2ed94a8b

Array(2, 2) Array(2, 3)





Array에 foreach를 사용할 수 있다. 

val array = Array.fill[Int](5){scala.util.Random.nextInt(5)}
var sum = 0
array.foreach(sum += _)
println(sum)

array foreach(i => println(i))

array foreach{
case i => println(i)
}


Posted by '김용환'
,