336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Slick 으로 DB CRUD 를 해보고 있는데


DateTime 을 읽을 때 아래와 같은 오류가 난다.


java.time.format.DateTimeParseException: Text '2019-07-24 00:00:00' could not be parsed at index 10


"scala slick date" 키워드로 구글링 검색 결과, Slick 3.3.2 Upgrade Guide 가 나온다.  일단 들어가본다


https://scala-slick.org/doc/3.3.2/upgrade.html


사용중인 DBMS 는 mySQL.


Upgrade Guide 의 slick.jdbc.MySQLProfile 부분에 보면 DateTime -> LocalDateTime 으로 Parsing 될 때 


어떤 타입의 Texts를 읽는지 나온다. 


java.time.LocalDateTime TEXT '2019-02-03T18:20:28.661'


즉 yyyy-mm-dd'T'hh:MM:ss 형태로 있어야 하는데, T 가 없어 10번째 인덱스를 로딩하는중에 에러가 났던 것.


DB 를 바꾸긴 힘들고, Slick 로딩부분을 수정할 방법을 찾는다. 


Upgrade Guide 에서 제공하는 링크를 참고하여 코드 수정 시작 

(참고 : https://github.com/d6y/instant-etc/blob/master/src/main/scala/main.scala#L9-L45)



import java.sql.ResultSet
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

trait NewMySQLProfile extends slick.jdbc.JdbcProfile with slick.jdbc.MySQLProfile {
  override val columnTypes = new JdbcTypes
  class JdbcTypes extends super.JdbcTypes {
    override val localDateTimeType : LocalDateTimeJdbcType = new LocalDateTimeJdbcType {
      override def getValue(r: ResultSet, idx: Int) : LocalDateTime = {
        val pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        r.getString(idx) match {
          case null => null
          case iso8601String => LocalDateTime.parse(iso8601String, pattern)        }
      }
    }
  }
}

object NewMySQLProfile extends NewMySQLProfile


코드는 전부 올려두었으니, 분석은 이거 읽는분이 알아서... 


그리고 Slick 의 코드에 



import slick.jdbc.MySQLProfile.api._ 

이 부분을 


import NewMySQLProfile.api._ 

로 바꾸면 된다. 패키지 & 클래스명은 자기의 패키지에 맞게 수정하시길.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1. 빌드 툴 

 - SBT, 스칼라는 라이브러리 의존성 및 빌드를 위해 SBT 를 사용, 자세히 까지는 아니어도, 대략적으로라도 알고 있어야 함 

   https://github.com/sbt/sbt


2. Typesafe Config 

 - 스칼라에서는 환경 설정등을 읽어들이기 위해 Typesafe 를 사용함, resources 안에 application.conf 를 넣어서 관리하거나, 다른 방법으로 관리할 수 있음.

    https://github.com/lightbend/config


3. 그 밖의 라이브러리 & 프레임워크 

 - cats : 이런저런 일을 해주는 라이브러리 

 - slick : DB 접근 라이브러리 

 - play framework : 웹 서버 프레임워크

 - akka : actor 기반 작업용 프레임워크 


기본기를 잘 쌓자. typesafe 부터 반복연습

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1. 액터 

  - 각자의 특정 작업을 수행, 각자의 Queue 를 갖고있고 dequeue 한 후 작업을 수행 


2. Supervisor 

  - 액터들의 상태를 감시하는 액터. 계층상 상위에 있는 액터는 슈퍼바이저가 된다. 


3. 테스트 

  - SilentActor -> 메세지를 받기만 하고 외부로 아예 안내보내는 액터, underlyingActor 으로 접근해 확인하거나 , expectMsg 함수호출해서 확인한다. 

  - SendingActor -> 받은 메시지를 다른 액터에 보낼 때 사용하는 방법, expectMsgPF 사용한다. 

  - SideEffectActor -> Side Effect 가 있는경우 사용 (가령 함수 내에서 로그 메시지를 출력한다거나..), 리스너를 사용할 수 있다. 


4. 내고장성 

  - 액터에 여러가지 문제가 생겼을 때, 어떻게 작동할 지 설정할 수 있다. 

  - 재시작, 종료, 진행, 위로전달 전략을 설정할 수 있으며, OneForOneStrategy 는 한개의 액터에만, AllForOneStrategy 에는 자기 휘하의 모든 액터를 종료한다.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

코세라 스칼라 강좌 4.4 Variance 중.


Typing Rules for Functions 


If A2 <: A1 and B1 <: B2 then

    (A1 => B1) <: (A2 => B2)


뭔 소린지 전혀 몰라서 수시간을 고민해보다가 stackoverflow 보고 이해 


원문 주소는 https://stackoverflow.com/questions/41098105/subtyping-between-function-types 여기


내가 이해한 방식으로 정리한다. 


1. subtype 이다? 

  - subtype 이란건 'a 는 b 이다' 의 is - a 관계가 성립하는거.


2. (A1 => B1) <: (A2 => B2) 임을 증명하려면 

  - '(A1 => B1) 함수(이하 F1)는  (A2 => B2) 함수(이하 F2)다' 가 증명되면 됨.

    

'F1 는 F2 이다 ' 가 true 가 되려면 필요한 규칙은 


F2(a) 를 F1(a) 로 바꿔도 에러 없이 정확한 값이 나와야 한다는 소린데(리스코프의 치환 원칙), 이소리는 즉


1. F2 의 모든 input 이 F1 에서 에러가 없어야 함

2. F1 의 모든 output 이 F2 의 output 이 되어야 함


저 규칙에 맞나 보면 

1. F2 의 모든 input 이 F1 에서 에러가 없어야 함

  -> 만족한다. A2(F2 의 input) <: A1 (F1 의 input) 이기 때문에, A2 는 A1 의 부분집합이고, A2의 모든 원소는 A1 이다.

2. F1 의 모든 output 이 F2 의 output 이 되어야 함 

  -> 만족한다. B1(F1 의 output) <: B2(F2 의 output) 이기 때문에 B1 은 B2 의 부분집합이고, B1 의 모든 원소는 B2 이다.


이 때문에 'F1 은 F2 이다' 는 참이고, F1 은 F2 의 subtype 이다.


맞나 이거? 


    

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

다시 한번 복습 


A <: B 이고 C[A] <: C[B] 인걸 Covariant

A <: B 이고 C[A] >: C[B] 인걸 Contravariant


Contravariant 는 주로 함수를 파라미터로 넘길 때 일어난다고 이야기 함.


이걸 그림으로 다시 생각 해보자, 일단 아래와 같은 클래스가 있다. 


class Animal {}

class Bird extends Animal {}
class Chicken extends Bird {}
class Duck extends Bird {}


이걸 밴 다이어 그램으로 그려보면 아래와 같이 된다.



이제 함수를 선언해보자. 

object HelloWorld {
  type FunctorType[A] = (A => String) //함수타입 한개 선언

  val animalFunc : FunctorType[Animal] = { a => "Animal" }
  val birdFunc : FunctorType[Bird] = { a => "Bird" }
  val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
  val duckFunc : FunctorType[Duck] = { a => "Duck"}
}


위의 밴 다이어 그램을 보면서 다시 정리해보자 


Animal >: Bird 이면, FunctorType[Animal] 과 FunctorType[Bird] 의 관계는?


FunctorType[Animal] 을 풀어보면 Animal => String 이고, 

FunctorType[Bird] 을 풀어보면 Bird => String 인데, 

위의 벤 다이어그램에서 보면, Bird 는 Animal 의 부분집합 이니까, 모든 Bird 는 Animal 이다. 따라서, 


FunctorType[Bird]  는 Bird => String 도 될 수 있고, Animal => String 도 될 수 있다. 


때문에, FunctorType[Animal] <: FunctorType[Bird] 이 되고, 


FunctorType 은 contravariant 가 된다... 

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

코세라 강의 보는 중에 


A <: B 이고 C[A] <: C[B] 인걸 Covariant 

A <: B 이고 C[A] >: C[B] 인걸 Contravariant 라고 한다.


Covariant 는 이미 알고있는 거고,

Contravariant 가 무슨소린지 도저히 모르겠어서, 구글링... 하다가 하나 발견... 4시간에 걸쳐 겨우 이해하고, 정리글을 남긴다. 


아래와 같이 클래스들이 있고, 함수 타입 한개를 선언한다.


그리고 몇가지 함수들을 선언한다.

class Animal {}

class Bird extends Animal {}
class Chicken extends Bird {}
class Duck extends Bird {}

object HelloWorld {
  type FunctorType[A] = (A => String) //함수타입 한개 선언

  val birdFunc : FunctorType[Bird] = { a => "Bird" }
  val animalFunc : FunctorType[Animal] = { a => "Animal" }
  val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
  val duckFunc : FunctorType[Duck] = { a => "Duck"}
}


이경우 클래스 관계는 


Animal <- Bird <- Chicken

Animal <- Bird <- Duck


이런 형식으로 된다. 다들 알다시피 


하지만 함수 아래와 같이 함수가 파라미터로 전달 될 때도, 다형성이 그대로 유지될까?

def test_contravariant(a: FunctorType[Bird]): Unit = {
    val ret1 = a(new Chicken);
    println(ret1)
    val ret2 = a(new Bird);
    println(ret2)
}



test_contravariant(animalFunc) 
test_contravariant(birdFunc) 
test_contravariant(chickenFunc)  //컴파일에러
test_contravariant(duckFunc)     //컴파일에러


맨 처음 봤을때는 의외의 결과였다. 그 전에 C++ JAVA 에서 써왔던 것과 반대로 나타났기 때문이다. 


내친김에 covariant 도 테스트 해본다. 


def test_covariant(a: Bird) : Unit = {
    println(a.toString)
}


예상한대로 결과가 나온다.


test_covariant(new Animal) //컴파일에러 test_covariant(new Bird) test_covariant(new Chicken) test_covariant(new Duck)


클래스 상속과 관련해서 생각을 다시 정리해본다. 


"

슈퍼클래스의 참조로 하위클래스의 객체를 생성하거나 파라미터로 전달해도 문제가 없는 이유는, 

하위클래스라고 해서 있던 public 함수/변수가 없어지거나 하지 않기 때문이다.

"


그리고 contravariant 예제를 다시한번 본다.


val animalFunc : FunctorType[Animal] = { a => "Animal" }
val birdFunc : FunctorType[Bird] = { a => "Bird" }
val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
val duckFunc : FunctorType[Duck] = { a => "Duck"}

def test_contravariant(a: FunctorType[Bird]): Unit = {
    val ret1 = a(new Chicken);
    println(ret1)
    val ret2 = a(new Bird);
    println(ret2)
}


test_contravariant 의 파라미터로 chickenFunc 나 duckFunc 를 넘기게 되면, 


Chicken 클래스에는 있는 public 함수지만, Bird 클래스에는 없는 public 함수가 있을 수 있다.

Duck 클래스에는 있는 public 함수지만, Bird 클래스에는 없는 public 함수가 있을 수 있다.


하지만 animalFunc 을 test_contravariant 로 넘기게 되면,

Animal 클래스에 있는 모든 public 함수는 Bird 클래스에 있다.


이때문에 FunctorType[슈퍼클래스] 에 대해서는 다형성이 일어나지만,

FunctorType[하위클래스] 에 대해서는 다형성이 일어나지 않는다. 

즉, 상속 관계가 역전이 된다. 


이런 경우가 A <: B 이고 C[A] >: C[B] 이며, 

이를 contravariant 라고 부른다. 




블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

play framework로 이런저런 작업과 스칼라 관련 모임에서 코드를  보는 와중에 

 

implicit 키워드가 눈에 자주 보인다. 

 

나중에 혼란스러워 하지말고 미리 정리.

 

- implicit parameter 

(참고 : https://docs.scala-lang.org/ko/tutorials/tour/implicit-parameters.html.html

먼저 메인 클래스를 선언한다.

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce();
  }
}

 

간단하다, Hello, world 문자열하나만 보여준다. testFunction 은 implicit 파라미터로 LiveObject 를 사용중이다.

testFunction 은 지금 당장은 사용하지 않으니 "이런게 있구나" 수준으로 넘어가자.

 

trait의 내용은 아래와 같다. 

trait LiveObject {
  def introduce() = {
    println("I am LiveObject")
  }
}

이제 LiveObject 를 믹스인한 클래스 두 개를 생성한다.

class Animal extends LiveObject {
  override def introduce(): Unit = {
    println("I am Animal")
  }
}

class Human extends LiveObject {
  override def introduce(): Unit = {
    println("I am Human")
  }
}

 

간단하다 introduce 를 재정의 해서 문자열 하나만 다르게 재정의 했다.

 

준비작업은 모두 끝났다. 이제 main 함수 내에서 testFunction 을 호출을... 했는데? 어라? 에러가 있네?

main 함수 안에서 testFunction 호출한 모습

에러 내용을 자세히 보면 

implicit 파라미터 없음.

직접 넣어줄 수는 있다.. 근데 이건 '암시적' 아닌 것 같아 다른 방법을 찾아본다.

 

바로 위에 Animal 변수를 하나 추가해준다.. 이러면 돌아가겠지.

 


object HelloWorld {

  def main(args: Array[String]): Unit = {
    println("hello, world!")
    val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

 

여전히 에러가 난다. 

 

여전히 변수를 못찾음.

암시적 파라미터 관련 스칼라 문서를 다시 읽어보면... 

 

스칼라 컴파일러는 implicit 파라미터를 만났을 때, 실제 파라미터에 할당할 변수를 검색하는데 규칙은 다음과 같다. 

1. 메서드가 호출되었을 때, prefix 없이 접근할 수 있는 변수 (당연히 변수들은 implicit 키워드가 붙어야 함.)

2. implicit 키워드가 붙은 암시적 파라미터와 관련된 모든 멤버 

 

2번은 조금 어려우니 일단 1번부터, 암시적 파라미터를 설정하려면 implicit 을 붙여야 한다. 변수를 implicit 으로 변경해서 다시.


object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

이번엔 실행이 제대로 된다.


  hello, world!
  I am thomas
  I am Animal
  Process finished with exit code 0

암시적 변수 / 함수를 선언하려면 implicit 키워드를 사용하여 선언해야 정상적으로 인식하는듯

 

그러면 이번에 같은 type 을 두 개 넣으면 어떻게 될까? 


def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val animal = new Animal
    implicit val human = new Human
    testFunction("thomas")
}

실행 해보니 아래와 같은 Exception 이 발생한다. 

 

같은 implicit 변수가 두개 선언되었을 때 Exception

머릿속에 어느정도 그림이 그려진다. 

 

implicit 파라미터는 "함수가 호출 된 타이밍" 에 prefix 없이 접근 가능한 변수 중 "가장 가까운 변수 " 가 할당되다 라고 이해하면 빠를듯.

 

다음번엔 implicit function 을 좀 더 봐야겠다.

 

2019년 11월 18일 추가 

 

변수의 scope 가 다를경우 같은 타입의 implicit 의 선언이 가능하다. 이 경우에 가장 최근에 선언된 implicit 변수가 사용된다.

 


object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val human = new Human
    overlapfunction()
  }

  def overlapfunction()(implicit liveObject : LiveObject) : Unit = {
    implicit val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

위 코드를 실행하면 아래와 같은 결과가 나온다.

 

 


hello, world!
I am thomas
I am Animal
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Play framework 버전 2.5.x 에서 구현한 내용입니다. 




Play Framework 로 DB 연동할 때 주로 사용하는 도구 Slick


에러 처리 관련하여 API 문서를 뒤져 알아낸 사실들 정리


로그인 처리를 위해 Account 테이블에 쿼리를 날리는 함수 등록 


def verifyPassword[A, B](account : String, password : String) = {

  db.run {

    accounts

      .filter(record => record.account === account && record.password === password)

      .map(_.userName)

      .result

  }

}


그리고 Controller 에서 verifyPassword 호출


val loginData = request.body

ac.verifyPassword(loginData.account, loginData.password).map {

  a => if (a.nonEmpty) Ok(a.toString) else Ok("NoUser")

}


에러사항 없이 잘 돌아간다.... 근데 항상 좋은일만 생길일은 없는 법


PK 가 겹칠수도 있고

DB 서버가 맛이 가, 커넥션이 끊길수도 있고

트래픽이 많이 몰려 타임아웃이 날 수도 있고 

테이블 이름을 잘못 써 오류가 날 수도 있다.


그때마다 Play framework 는 에러를 적나라하게 보여준다. 



에러 발생 시, 적나라하게 보여준다.


 쪽팔림을 막고, 행여나 터질 보안이슈를 대비해 저런 에러들을 수정해보자.


 일단 구글에서 Slick error handling 으로 검색, 


http://slick.lightbend.com/doc/3.1.0/dbio.html  여기를 보면 아래와 같이 써있다. 


Note

For even more flexible error handling use asTry and failed. Unlike with andFinally and cleanUp the resulting actions cannot be used for streaming.


좀 더 유언하게 에러 처리를 하려면 asTry 랑 failed 를 사용해라...


일단 asTry 부터, asTry는 맨 마지막에 .asTry만 추가해주면 된다. 자세한 내용으 API 문서 참고 바람.


def verifyPassword[A, B](account : String, password : String) = {

  db.run {

    accounts

      .filter(record => record.account === account && record.password === password)

      .map(_.userName)

      .result

      .asTry

  }

}


그리고 호출부에서 결과 처리부분을 추가한다. 


val loginData = request.body

ac.verifyPassword(loginData.account, loginData.password).map {

  _ match {

    case Success(a) => if (a.nonEmpty) Ok(a.toString) else Ok("NoUser")

    case Failure(t) => Ok("Failure!!")

  }

}


테스트... 해보면 의도했던 화면들이 나온다.... 


마지막으로, 


에러처리를 위해 failed 를 쓰라고 했는데... 이건 어떻게 쓰는지 다음에 찾아봐야겠다.










블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

람다 대수 관련 자료를 찾아보면 다음과 같은 규칙이 있다. 


(x, y -> ax + by) 는 (x -> y -> ax + by) 형태로 바꿀 수 있으며 이를 커링 이라고 한다. 


Scala 를 공부하면서 가장 많이 접하는 형식인데, 지금까지 내가 쓰고있던 명령형 언어들(C/C++) 과는 형식이 많이 달라 혼란스러웠지만, 이제 머릿속으로 정리되는 듯 하여 글로 남긴다. 



대체 커링은 왜 쓰는 것인가?


Scala 공부하는 내내 '커링 이걸 대체 왜 쓰고, 어디다가 쓰는 것인가?' 란 생각이 가장 많이 들었고 오늘에서야 그 답을 찾았다. 행여나 이 글을 읽는 분께는 아래 설명이 '절대적인 정답' 이 아닌 '쓰임새 중 하나' 로 이해하시길 바란다. 


쓰임새 중 하나 : 디폴트 파라미터에 적용할 수 있다. 


우선 C++ 코드를 보자, 공간이 없어 full code 는 올리지 않겠다.

(나중에 시간이 나면 gut hub 같은데 올릴수도..)


int Adder(int num1=1, int num2=2)
{
  return num1+num2;
}


쉽게 알 수 있듯 파라미터로 넘어온 num1 과 num2 의 합을 리턴해주는 코드다.

위 C++코드엔 몇가지 제약사항이 있다. 


1. default 파라미터 뒤에, default 가 아닌 파라미터가 오면 안된다. 

  =>int Adder(int num=1, int num2 = 2, int num3) 은 안된다는 소리다(컴파일에러)

2. 매개변수가 주어지지 않을 때, default 파라미터는 왼쪽 변수부터 적용이 된다.

  => Adder(3) 으로 호출이 되었을 때 num1 의 값은 3이고, num2 의 값은 2이다.

  => num1 에는 default, num2에는 4 값을 사용토록 하려면 Adder(1,2) 같은 형태로 호출해야 한다.


Scala 에선 커링을 이용하면 이를 좀 다른 방법으로 사용할 수 있다.

(주, Scala 에서는 언어 차원으로 default parameter를 지원한다. 

링크 :  http://docs.scala-lang.org/ko/tutorials/tour/default-parameter-values )


위의 C++ 코드와 같은 기능을 하는 Scala 함수를 생각해보자 


def Adder(x : Int, y : Int) : Int = x + y


이 함수를 커링하면

def AdderDefaultX(x : Int)(y : Int) : Int = x + y

혹은

def AdderDefaultY(y : Int)(x : Int) : Int = x + y

이렇게 된다.


이후, x 값을 100 이 default  로 정해놓고 싶다면 


val AdderX100 = AdderDefaultX(100)


y 값을 100 으로 정해놓고 싶다면 


val AdderY100 = AdderDefaultY(100)


이렇게 하면 된다. 


위의 예시는 '그냥 이렇게 쓴다' 란 정도로 설명을 해 둔것이고. 


라이브러리 등의 구현(여러명이 같이 작업할 코드들)에서


'이 함수는 불려질 때 반드시 어떤 파라미터가 어떤 값이 되어야 함.' 이 호출 규칙일 때 


위 아래로 주석을 달거나, 문서화를 하기 보다는 


특정 파라미터를 특정 값으로 세팅한 함수(위의 Adder 함수들 같이)로 커링하여 외부에 노출 시킨다면 


안정성이 훨씬 증가할 듯 보인다. 


추신 : 이 글은 절대적인 글이 아니니, 수정사항이나 틀린부분 있으면 언제든지 지적을 부탁드립니다.


블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,