scala에서 json4s를 간단하게 사용할 때, Map[String, Any]으로 파싱할 때 특별히 에러가 발생하지 않는다.


간단히 동작하는 예시는 다음과 같다. 



import
org.json4s._
import org.json4s.JsonAST.{JObject, JValue}
import org.json4s.jackson.{Json, JsonMethods}

implicit val formats = DefaultFormats
val source = """{"comment_type":"general"}"""
val commentType = JsonMethods.parse(source) .extract[Map[String, Any]].get("comment_type")
println(commentType)



하지만, Spark에서 사용하면 에러가 발생한다. 


org.json4s.package$MappingException: No usable value for... 




이런 비슷한 얘기가 json4s 이슈에 있다. 

https://github.com/json4s/json4s/issues/124




그래서 Map이 아닌 case class를 사용하면 또 에러가 발생한다. 


기본 값을 넣어줘야 에러가 발생하지 않는다. 



case class Comment(comment_type: String = "general")


좀 더 고급스럽게 Option을 추가한다.


case class Comment(comment_type: Option[String] = Some("general"))



 잘 동작한다. Map 대신 case class를 사용하기를 추천한다. 


import org.json4s._
import org.json4s.JsonAST.{JObject, JValue}
import org.json4s.jackson.{Json, JsonMethods}

case class Comment(comment_type: Option[String] = Some("general"))

implicit val formats = DefaultFormats
val source = """{"comment_type":"general"}"""
val commentType = JsonMethods.parse(source).extract[Comment].comment_type
println(commentType)



Spark에서 이렇게 쓰면 아주 잘 작동할 것 같았지만, 이슈가 있다. 다른 필드 파싱할 때, 예상되는 타입과 실제 값과의 차이로 파싱 에러가 발생할 수 있다.



이럴 때는 Try문를 추가하고, Comment에 해당되는 모든 필드에 대해서 Option 처리를 추가해야 한다. 어떤 값과 상관없이 처리할 수 있도록 해야 한다.


case class Comment(comment_type: Option[String] = Some("general"), action: Option[String] = Some("create", ....)

Posted by '김용환'
,


scala의 json4s 사용시 아래와 같은 에러가 발생하면, 필드에 대한 오타가 발생했다.



Exception in thread "main" org.json4s.package$MappingException: No usable value for xxx

Did not find value which can be converted into java.lang.String


예를 들어, json source의 내용의 comment_type이고, case class은 comments_type으로 잘못 매핑 구조를 만들면, 에러가 발생한다. 



예)


val source = """{"comment_type":"general"}"""



case class Comment(comments_type: String)

JsonMethods.parse(source).extract[Comment].comment_type


Posted by '김용환'
,




json4s를 spark에 쓸 때, 버전 관리를 잘 해야 한다. scala 1.5.x, 1.6.x은 json4s의 3.2.x만 사용할 수 있다.

그 이상의 버전을 사용하면, java.lang.NoSuchMethodError: org.json4s.jackson.JsonMethods$.parse$default$3()Z 예외가 발생한다.





https://github.com/json4s/json4s/issues/316


Spark 1.5.0 depends on json4s 3.2.10. json4s 3.2.10 and 3.3.0 does not have binary compatibility. You can't use spark 1.5.0 and json4s 3.3.0 together.

Posted by '김용환'
,

[scala] json 이슈

scala 2016. 10. 12. 19:18




1) play-js




2) json4s


scala json 라이브러리 중 하나인 json4s 3.2~3.4를 사용하고 있다.


  val json = parse(""" { "type": "general", "name":"sticon"} """)


json.extract[Map[String, Any]]로 호출할 때, No information known about type at ... Exception이 난다.


원인은 library 버전 의존성 또는 library 이슈로 생각된다.



https://github.com/json4s/json4s/issues/124




3) pickling


https://github.com/scala/pickling

Posted by '김용환'
,

 flatMap은 filter+map의 역할을 할 수 있다. 


filter를 먼저하고, map을 하는 일반적인 코딩이다.

val list = List(1,2,3,4,5,6,7,8,9,10)
val result = list.filter(
x => 0 == (x % 2)
).map(
x => x + 1
)

println(result)

결과는 다음과 같다. 


List(3, 5, 7, 9, 11)




flatMap과 if문으로 변환하면 다음과 같다. 

val flatMapResult = list.flatMap(x =>
if (0 == (x % 2)) {
Some(x+1)
} else {
None
}
)

println(flatMapResult)


결과는 위와 동일하고, 훨씬 깔끔한 느낌이다.




List(3, 5, 7, 9, 11)





filter->map을 쓰는 조합에서 flatMap으로 바꿔야 했던 사례가 있었다.


Spark으로 대용량 처리하던 중, 

filter에서 사용하고, map에서도 사용해야 하는 중복 코드가 발생할 수 있는데, 이를 깔끔하게 중복처리하지 않으려면, flatMap을 사용하면 중복 코드가 발생하지 않는다. 






Posted by '김용환'
,


scala는 groupBy api를 제공한다. 정확히 말하면, TraserableLike.groupBy을 사용할 수 있다.


println("java scala".trim.groupBy(identity))



identity는 정의된 함수를 사용하여 단어별로 groupBy를 할 수 있다.

def identity[A](x: A): A = x// @see `conforms` for the implicit version


groupBy 결과는 다음과 같다.


Map(s -> s, j -> j, a -> aaaa,   ->  , v -> v, l -> l, c -> c)



카운트를 계산하기 위해서는 mapValues를 이용해 본다. 



println("java scala".trim.groupBy(identity).mapValues(_.size).toVector)

결과는 다음과 같다.


Vector((s,1), (j,1), (a,4), ( ,1), (v,1), (l,1), (c,1))





3 개의 값을 가진 튜플에 대해서 groupBy를 호출한다. 

val list = Seq(("one", "i", "char"), ("two", "2", "num"), ("two", "ii", "char"), ("one", "1", "num"), ("four", "iv", "char"), ("five", "iv", "char"))


val v = list.groupBy(_._3)
println(v)

val values = list.groupBy(_._3).mapValues(_.map(_._2))
println(values)



첫 번째 groupBy 결과는 다음과 같다.


Map(num -> List((two,2,num), (one,1,num)), char -> List((one,i,char), (two,ii,char), (four,iv,char), (five,iv,char)))



두 번째 groupBy-mapValues 결과는 다음과 같다.


Map(num -> List(2, 1), char -> List(i, ii, iv, iv))





키 별 개수를 알고 싶다면, 다음과 같은 코드를 사용할 수 있다.


val values1 = list.groupBy(_._3).map(t => (t._1, t._2.length))
println(values1)



Map(num -> 2, char -> 4)




Posted by '김용환'
,

[scala] retry

scala 2016. 10. 6. 19:20

spring4에서는 Retryable을 사용하면 retry 관련 코드가 짧아진다.


 @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 0))
String call(String url);




scala에서는 아예 쉽게 retry를 쉽게 만들 수 있다. 


아래 링크를 참조하면 좋은 결과를 얻을 수 있다. 꼬리 재귀와 커링을 사용했다. 


http://stackoverflow.com/questions/7930814/whats-the-scala-way-to-implement-a-retry-able-call-like-this-one



object Main extends App {
@annotation.tailrec
def retry[T](n: Int)(fn: => T): T = {
util.Try { fn } match {
case util.Success(x) => x
case _ if n > 1 => retry(n - 1)(fn)
case util.Failure(e) => throw e
}
}

var i = 0;
retry(3) {
i += 1
test(i)
}

def test(i: Int): Unit = {
if (i == 1 || i == 2) throw new Exception("XXX")
if (i == 3) {
System.out.println(i)
}
}
}


결과는 3이다. 








Posted by '김용환'
,


스칼라의 HashSet은 자바의 HashSet보다 빠른 것으로 알려져 있다.

그 이유는 HashSet의 구현이 해시 트라이(Hash trie)로 구현되어 있다. 


참고로 해시 트라이는 스칼라 공식 문서에서 간단히 소개하고 있다. 


http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#hash-tries


Their representation is similar to vectors in that they are also trees where every node has 32 elements or 32 subtrees. But the selection of these keys is now done based on hash code. For instance, to find a given key in a map, one first takes the hash code of the key. Then, the lowest 5 bits of the hash code are used to select the first subtree, followed by the next 5 bits and so on. The selection stops once all elements stored in a node have hash codes that differ from each other in the bits that are selected up to this level.






또한, 해시 트라이는 속도를 빠르게 개선하는 분할상환 상수 시간( amortized constant time)인데, 

원래 Hash가 검색은 빠르지만, 추가/삭제가 나쁜 구조인데, 스칼라는 이를 개선했다. 


http://docs.scala-lang.org/overviews/collections/performance-characteristics.html




 lookupaddremovemin
immutable    
HashSet/HashMapeCeCeCL



aCThe operation takes amortized constant time. Some invocations of the operation might take longer, but if many operations are performed on average only constant time per operation is taken.


여기 나오는 분할상환 시간 분석에 대해서는 아래 위키를 참조하거나 인터넷을 참조한다. 

분할상환 시간 분석은 알고리즘의 전반적인 연산 집합에 대해 비용이 높은 연산, 그리고 비용이 덜한 연산 모두를 함께 고려하는 기법이다.


https://ko.wikipedia.org/wiki/%EB%B6%84%ED%95%A0%EC%83%81%ED%99%98%EB%B6%84%EC%84%9D






또한 아래 자료에도 분할상환 시간에 대한 시간에 대해서 설명되어 있다. 


http://scabl.blogspot.kr/2014/10/what-heck-is-amortized-constant-time.html








Posted by '김용환'
,


대수적 자료형(ADT)를 시작할 때 참조한 문서를 기반으로 공부했다. 

http://tpolecat.github.io/presentations/algebraic_types.html







스칼라 문서를 보다보면, Algebraic Data Types, ADT(대수적 자료형)이 언급된다. 

ADT를 이해하려면 카테시안 곱의 이해를 배경으로 한다.  먼저 수학적인 집합부터 얘기해 본다.


(참조)

https://en.wikipedia.org/wiki/Algebraic_data_type




먼저 Ordered Set(순서 집합)이라는 것을 보자.

Ordered Set은 2 개 이상의 변수가 순서를 가진 함수 또는 레코드를 표현한 타입이다. 

(x, y) 이런 형태를 가지고 있다. 




이제, Cartesian Product(카테시안 프로덕트, 카테시안 곱, 곱 집합)을 이해해 보자. 


SQL에서 Join을 다룰 때 나오는 용어와 같은데, 두 집합 A와 B의 원소가 있을 때, 이를 통해 만들어지는 모든 순서쌍 (a, b)들의 집합이다. 수학적으로 표현하면, 'a∈A 이고 b∈B 인 모든 순서쌍 (a,b)들의 집합'이라 할 수 있겠다.

그리고, AxB라고 한다. 


A = { 1, 2}, B = {x, y} 라면, A x B = { (1,x), (1,y), (2,x), (2,y) }이다. 





이제, 스칼라로 집중해 본다.


스칼라의 특정 타입에 대한 '값의 개수'를 가진다. Nothing은 없고, Unit은 1개, Boolean은 true와 false, Byte는 256개, String은 엄청나게 많은 값의 개수를 가진다. 


카테시안 곱을 사용하면, 아래와 같은 타입을 가질 때 가질 수 있는 모든 타입의 개수는 다음과 같다. 


(Unit , Boolean) = 1 × 2 = 2 

(Byte , Boolean) = 256 × 2 = 512



ADT는 합성된 타입(Composit Type)을 기반으로 유한한 값의 개수와 잘 정해진 타입으로 이루어지는 형태를 말한다. 즉, 집합적 개념에서 보면, 모두 합쳐면 모든 세상을 만들 수 있는 것을 말한다. 


Option이 대표적인 ADT라 할 수 있다. Option은 다양한 값을 표현하는 Some과 아무 것도 없음을 의미하는 None으로 만들어져 있다. 이와 비슷하게 Either와 Try가 있고, List는 Lil과 일반 리스트가 존재한다. 



sealed trait Option[+A] case object None extends Option[Nothing] case class Some[A](a: A) extends Option[A]



sealed trait Either[+A, +B] case class Left[A](a: A) extends Either[A, Nothing] case class Right[B](b: B) extends Either[Nothing, B]




sealed trait Try[+A] case class Success[A](a: A) extends Try[A] case class Failure[A](t: Throwable) extends Try[A]




sealed trait List[+A] case object Nil extends List[Nothing] case class ::[A](head: A, tail: List[A]) extends List[A] object List { def apply[A](as: A*): List[A] = ... }







스칼라에서 ADT는 sum type을 의미한다. 


아래 코드 처럼 Pet이라는 sealed trait가 있고, Pet은 딱 3 종류의 타입 Cat, Fish, Squid만 존재하게 한다. ADT에서 말한대로 한정된 타입이 존재한다. 


스칼라에서 Pet 하위 클래스를 사용할 때, ADT와 스칼라 컴파일러 간의 연관성을 확인해 본다.


sealed trait Pet
case class Cat(name: String) extends Pet
case class Fish(name: String, color: String) extends Pet
case class Squid(name: String, age: Int) extends Pet

val bob: Pet = Cat("matt")

스칼라에서의 ADT 타입은 sum type을 의미하는데, 특정 상위 클래스를 상속한 여러 case class 클래스로 만드는 경우를 의미한다. 


Encoding을 Pet <-- Cat, Fish, Squid로 진행하고,

Decoding을 Pet match { ... } 으로 진행하는 경우이다. 



sum type은 tagged_union(태그 유니온, 혹은 꼬리가 붙은 유니온)이라 불린다.  자세한 내용은 위키를 참조한다. 


(참고)

https://en.wikipedia.org/wiki/Tagged_union






예문을 실행해본다.



Pet의 하위 클래스인 Cat과 Squid에 대한 패턴 매칭을 시행한다. 

object Pet {
sealed trait Pet
case class Cat(name: String) extends Pet
case class Fish(name: String, color: String) extends Pet
case class Squid(name: String, age: Int) extends Pet
}

object Main extends App {

import com.google.Pet._

val bob: Pet = Cat("Matt")

def sayHi(pet: Pet): String =
pet match {
case Cat(n) => "Meow " + n + "!"
case Fish(n, _) => "Hello fishy " + n + "."
case Squid(n, _) => "Hi ssss " + n + "."
}

println(sayHi(Cat("Bob")))
println(sayHi(Squid("Steve", 10)))
}

결과는 다음과 같다. 


Meow Bob!

Hi ssss Steve.




하지만, Squid 에 주석을 달면 컴파일 에러가 난다. 



object Pet {
sealed trait Pet
case class Cat(name: String) extends Pet
case class Fish(name: String, color: String) extends Pet
case class Squid(name: String, age: Int) extends Pet
}

object Main extends App {

import com.google.Pet._

val bob: Pet = Cat("Matt")

def sayHi(pet: Pet): String =
pet match {
case Cat(n) => "Meow " + n + "!"
case Fish(n, _) => "Hello fishy " + n + "."
//case Squid(n, _) => "Hi ssss " + n + "."
}

println(sayHi(Cat("Bob")))
println(sayHi(Squid("Steve", 10)))
}


런타임 아닌 컴파일 타임 때 exhautive 에러를 발생한다. (전문 용어로 패턴 매칭의 Exhaustiveness Checking 이라 한다) 컴파일러로 하여금 코드를 신뢰성 있게 개발할 수 있도록 도와준다.

 

Warning:(39, 5) match may not be exhaustive.

It would fail on the following input: Squid(_, _)

    pet match {








ADT에 대해서 감이 잡혔으면, 아래 블로그로 공부하면 감이 더욱 잡힌다.


https://bertails.org/2015/02/15/abstract-algebraic-data-type/




Posted by '김용환'
,

스칼라 문서에 by name parameter라는 게 있다. 문법적인 의미라서 크게 와닿지는 않는다. 문법 정도만.. 



4.6.1 By-Name Parameters 


Syntax: ParamType ::= ‘=>’ Type The type of a value parameter may be prefixed by =>, e.g. x: => T . 


The type of such a parameter is then the parameterless method type => T . This indicates that However, at present singleton types of method parameters may only appear in the method body; so dependent method types are not supported. Basic Declarations and Definitions the corresponding argument is not evaluated at the point of function application, but instead is evaluated at each use within the function. 


That is, the argument is evaluated using call-by-name. The by-name modifier is disallowed for parameters of classes that carry a val or var prefix, including parameters of case classes for which a val prefix is implicitly generated.


The by-name modifier is also disallowed for implicit parameters (§7.2). Example 4.6.2 The declaration def whileLoop (cond: => Boolean) (stat: => Unit): Unit indicates that both parameters of whileLoop are evaluated using call-by-name.






by-name parameter는 함수를 매개변수로 보내고 해당 함수를 lazy evaluation을 할 수 있는 기법이다. 


더 쉽게 말하면 매개변수의 계산을 지연하고 싶어할 때 사용할 수 있다 .





아래 웹 페이지가 이해하는 데 많은 도움이 되었다.


https://groups.google.com/forum/#!topic/scala-korea/qyjq7IEdwRc

http://daily-scala.blogspot.kr/2009/12/by-name-parameter-to-function.html



def takesFunction(functionByName: () => Int) = println(functionByName())
takesFunction(() => 10)

by-name 매개변수는 :() => 형태를 가지고 있고, 결과는 10이다. 그리고 :() => 매개변수를 :=>으로 변경할 수 있고, 사용할 때는 깔끔하게 리턴 값만 넣을 수 있다.



def takesFunction(functionByName: => Int) = println(functionByName)
takesFunction(10)



래핑한 send 함수를 이용한다.


def takesFunction(functionByName: () => Int) = println(functionByName())
def send(x: => Int) = takesFunction(() => x)
send{10}


래핑한 send 함수를 사용해서 결과를 만들 수 있다. 

def takesAnotherFunction1(functionByName: => Int) = println(functionByName)
def sendAnother1(x: => Int) = takesAnotherFunction1(x)
sendAnother1{10}


다른 형태로도 사용할 수 있다. 


def takesAnotherFunction1(functionByName: => Int) 
                               = println(functionByName)
def sendAnother1(x: => Int) = takesAnotherFunction1(x)
sendAnother1{10}

def takesAnotherFunction2(functionByName: => Int) = println(functionByName)
def sendAnother2(x: => Int) = takesAnotherFunction2(x)
sendAnother2{10}



Posted by '김용환'
,