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


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


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




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


신호와 소음의 저자를 알게 된 것은 미국의 한 데이터 사이언티스트가 방한 후 네이트 실버를 추천하면서, 신호와 소음이라는 책을 알려주었다.


이후에 나는 네이트 실버라는 사람에 대한 궁금증와 계속 고민해 오던 통계에 대한 통찰력을 얻고 싶었다. 단순히 통계는 수학이 아닐터인데, 관련된 대부분의 책은 '수학의 정석' 마냥 문제와 문제 풀이에만 집중하고 있는 것들이 왠지 모르게 불편했다.


그런 나의 불편을 알듯이 이 책은 그 부분을 잘 설명해준 책이다. 이 책을 읽고 마지막을 읽는 순간까지 즐거웠다. 이 사람이 아마추어지만 프로에 뒤지지 않는 그의 통찰력에 감탄했다. 


훌륭한 도박사와 과학자가 추구하는 방법이 같다라고 주장할 정도로, 어쩌면 plausible reasoning에 대한 생각을 내게 전달해주어서 개인적으로 네이트 실버에게 감사하다라고 말하고 싶다. 


또한, 베이지안 추론에서 제일 중요한 것은 사전 통계이다. 생각보다 많은 기술 서적에서 이런 내용을 사실 잘 얘기하지 못하고 있었는데, 신호와 소음에서는 아주 잘 다뤘다. 


그리고, 베이즈와 흄이 동시대 사람이었던 것,  짧지만 베이즈의 삶에 대해서 언급해줘서 왜 그렇게 노력했는지도 좀 알게 되었다. 



먼저 통계나 베이지안 추론에 관심이 있는 개발자는 먼저 이 책을 읽고 다른 기술 서적을 봐도 좋을 것 같다. 







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



개연적 추론

개연적 추리

그럴만 하겠군





Induction [2] (귀납)은 그럴만하겠구나 방식으로 생각하는(plausible reasoning) 방식의 하나의 대표적인 예다.


연적 추론은 (논리적 엄밀성)을 보장하지 않으므로 사례를 통한 검증과 연역적 증명으로 논리적. 엄밀성을 (보완)해야 한다.


http://domath.kr/wiki/index.php/MPR_Intro#cite_note-2



http://m.blog.naver.com/selrim11/50106269307



더 읽어볼 거리

http://zolaist.org/wiki/index.php/%EA%B7%80%EB%82%A9%EC%9D%98_%EB%AC%B8%EC%A0%9C

Posted by '김용환'
,