스칼라 책에 대해 소개드리면 (제가 보유하고 있는 것 위주로)

러닝 스칼라(번역됨)로 시작하고
Programming in Scala (번역됨) 로 보충
- 각종 인터넷 글들로 보충 

하면 문법은 됩니다. 

이후에 

스칼라로 배우는 함수형 프로그래밍 (번역됨)
함수형 & 반응형 도메인 모델링 
스칼라 동시성 프로그래밍 (번역됨)

같은 책으로 감각을 익히면서  ( FPiS 빨강책은 매우 어렵습니다. 또한 '니가 알아서 이해해' 라는 불친절한 면도 있습니다. .결정적으로 스칼라 개발의 필독서는 아닙니다.)  


- 러닝 아카 (번역됨)
- Akka in Action
- Reactive Web Applications: Covers Play, Akka, and Reactive Streams
- Play for scala (번역됨)
- 스칼라와 기계학습 (번역됨)


 류의 책으로 응용하시면 됩니다. :-) 


스칼라로 배우는 함수형 프로그래밍 (일명 FPis 빨강책) 의 경우 보충하자면 

제가 생각하는 저 책(FPiS) 의  단점은 책의 내용중에 문제풀이가 많은데 답이 책에 없기 때문에 인터넷을 옆에 끼고 문제에 대한 답을 확인하면서 책을 봐야한다는 점. (프린트해서 책에다 붙혀놓는 정성이 있으면 뭐~ㅎ)

또 하나는 어려워서 스칼라를 포기를 하게 만든 다는 점. 저 책 내용을 잘 알지 못해도 스칼라로 개발하는데 아무 상관이 없을 수 있습니다. 스칼라를 순수함수형으로 라이브러리도 잘 만들면서 고급지게 쓰려면 참 좋은 책이긴 합니다만 , 좀 더 편리한 자바쯤으로 생각해서 객체지향으로 개발시라든지  Play2 및 Akka 로 응용 개발시에는 러닝 스칼라 한번 정도 훑어보고 시작해도 됩니다.   



실제 도메인 관점에서의 예를 통해 객체지향과 함수형 코드를 비교해보면, 어떤 분들에게는 좀 더 도움이 될 수 있을 거 같아서. 함수형& 반응형 도메인 모델링이라는 책에 있는 내용중 일부를 짧게 소개해 보려고 합니다.
-장,단점을 논하는 글이 아니며, 더 자세한 내용이 필요하다면 책을 참고하세요. 
-DDD 하면 전 아직도 장거리 직통전화 와 김혜림씨가 먼저 생각나네요 :-) 


개발자는 코드로 말하기 때문에 바로 코드 보겠습니다. 스칼라 코드이긴 하나 매우 간단하기 때문에 자바,C++,Python 개발자라면 무난히 이해 하실 겁니다만 약간의 설명은 추가 하겠습니다.

1. 객체지향 


은행등에서 계좌의 돈을 입금하고 출금하는 상황(도메인) 에 대한 코드입니다.
Balance 케이스 클래스는 기본적으로 불변입니다. 즉 내부의 account 는 불변임.
Account(계좌) 라는 클래스 내부에는 balance 라는 멤버변수(var로 선언되어있어서 이것은 변할 수 있다는 의미임. 개인적으로 스칼라는 불변에 대한 것을 덜 강조/강요하기 때문에, 이런 경우 변수명 뒤에 balance_var로 코딩컨벤션을 하는게 좋아보임) 와 출금(debit), 입금(credit) 메소드가 있습니다.

여기서 중요하게 볼 장면은 Account 에 돈을 입금하거나, 출금하는 행위(behavior)가 있을 때마다, 
Account 라는 객체의 내부 상태(state) 는 바뀐다는 점인데요.  (이것이 단점이다라고 말하는 것은 아니며, 일반적인 객체지향 모델링의 특징이라는 겁니다) 풀어서 말하면 내부의 balance 라는 멤버변수의 상태가 변하기 때문에, 객체 자체의 내부상태가 바뀐다고 말합니다.

2. 함수형 -1 

이제 함수형 예제를 보기전에 잠깐 이야기 하나 하자면요.
스타크래프트를 예로 들어보면, 
상대편의 드라군이 던진 공에 나의 마린이 맞으면, 마린의 상태는 변경되게 됩니다.
Marine이라는 객체가 있다고 가정하면 내부에 life 라는 멤버변수가 있으며, 타격을 받으면 life 는 줄어 들겁니다. 즉 내부의 상태가 바뀌게 되는데요.

하지만 함수형 모델링에서는 건강한 마린 -> 피가 20%정도 깍인 마린으로 "대체" 됩니다.
다시 반복해서 예를 들면  슈퍼맨이 조드장군한테 쳐 맞아서 상태가 안좋아지게되면, 그 슈퍼맨이 상태가 안좋아진게 아니라, 상태가 안좋아진 슈퍼맨을 새로 만들어서 대체해 버립니다. 이런 '짓' 에 거부감을 가져서 함수형을 싫어하는 사람들도 많다고 하네요.  대충 감이 잡히시죠?  (뭐가 항상 더 좋고 나쁘다라고 짤라 이야기 할 수 없습니다. 다만 동시성이 강조되는 현재에는 이런 방식으로 문제를 해결하려는 솔루션이 많아지고 있습니다.)

이제 내부를 변경하는 대신 새로운 녀석(계좌)을 만드는 예제를 보겠습니다. 

여전히 Account 클래스 내부에 debit 와 credit 메소드가 있습니다.
변한것은 변할 수 있는 멤버변수였던 balance 가 없어졌으며 대신 val 즉 불변의 멤버변수로 만들어 졌습니다. 
이에 따라서 debit 과 credit 가 반환하는 리턴 값에 큰 변화가 생겼습니다.

즉 입,출금 후에는 새로운 계좌(Account) 객체를 만들어서 반환하고 있네요. 네 상태가 바뀐 새로운 마린(계좌) 을 만들어서 반환하는 겁니다. 이전 마린(계좌)는 버리구요.

3. 함수형 -2

다음으로 볼 것은 위의 예제를 함수형 도메인 모델링 방식으로 리팩토링 할 것인데요.
함수형 도메인 모델링은 보통 "행위" 와 "상태" 를 분리 한다고 합니다. 

//------------- 상태와 행위를 디커플링 한다 --------------------// import java.util.{ Date, Calendar }
import scala.util. { Try, Success, Failure }

def today = Calendar.getInstance.getTime
type Amount = BigDecimal
case class Balance ( amount: Amount = 0)
//도메인 모델 : 상태를 책임 (멤버변수는 모두 immutable 하다)
case class Account(no : String, name: String, dateOfOpening: Date, balance: Balance = Balance())

//도메인 서비스: 행위를 책임 (내부 행위에서 부수작용을 일으키지 않는다) trait AccountService {
def debit(a: Account, amount: Amount): Try[Account] = {
if (a.balance.amount < amount)
Failure(new Exception("Insufficient balance in account"))
else
Success(a.copy(balance = Balance(a.balance.amount + amount)))
}

def credit(a: Account, amount: Amount): Try[Account] =
Success(a.copy(balance = Balance(a.balance.amount + amount)))
}
object AccountService extends AccountService
import AccountService._
val a = Account("a1", "John", today)
val b = credit(a, 10)

도메인 모델(상태)과 도메인 서비스(행위)로 나뉘어 졌습니다.
상태를 책임지는 immutable 한 Account 라는 클래스와  행위를 책임지는 AccountService. 

val b = credit(a, 1000) 을 보시면 , a 라는 계좌 객체에 10억을 입금했더니, a 라는 계좌객체가 부자가 된 것이 아니라, 부자가 된 b 객체를 리턴해주고 있습니다.

* 어쩌면 상태라는 것은 Discrete 된 것이라고 생각하고, 그것들을 관리하는데 있어서는 이게 더 옳을 수도 있고, 상태가 continuous 나 seamless 라고 생각하면 저렇게 하는 것은 말도 안되겠지요 ^^
* trait 는 자바8의 인터페이스라고 생각하시면 됩니다. 즉 has-a 이며 내부 구현이 가능하고, 그 자체로 실질적 객체생성은 못함.
* Try 는 스칼라에서 예외처리를 대신하는데 사용되는 놈으로, Try 내에는 성공객체 or 실패객체 둘 중 하나가 들어갑니다.  따라서 그것을 리턴 받는 쪽은 Try 가 성공인지, 실패인지를 확인해서 처리를 하게됩니다. 확인하는 과정 또한 행사코드 범벅이 되는 문제도 있는데 해결 방안도 공존합니다.

4. 함수형 -3  

마지막으로 설명할 것은 함수형 도메인을 가지고 요리(행위추가)하는 방법에 대한 것인데요. 
자바, 파이썬, 자바스크립트 등에서 사용되는 함수형 스타일에 대한 글을 한번도 읽어보지 않았다면 이해하기 힘드실 거에요 (적어도 map, foldLeft, reduce 가 뭔지 아시는분 대상)

val generateAuditLog: (Account, Amount) => Try[Strng] = //..
val write: String => Unit

debit (source, amount)
.flatMap( b => generateAuditLog(b, amount))
.foreach(write)

debit 은 출금된 이 후의 상태를 가진 새로운 Account 를 돌려준다고 말했잖아요? 슈퍼맨의 예로 기억을 다시 살려보세요. debit (source, amount) 를 호출해서 새로운 Account 를 리턴 받은후에 flatMap 을 적용하게 됩니다.  

아시다시피 Map 이나 flatMap 은 주로 List [A] 와 같이 어떤 타입을 감싸고 있는 타입에 대해 행동을 하는 놈인데요. 위에서는 Try[Account] , 즉 Account이라는 타입을 감싸고 있는 Try에 대해서 행위를 합니다. 

flatMap은 만약 debit 가 Try[Failure] 를 리턴한다면 시퀀스는 진행하지 않고 멈추며,  그렇지 않고 정상적으로  Try[Account] 리턴한다면 그것의 타입을 한단계 벗겨서 , 내부의 Account 를 b => generatedAuditLog 함수리터럴에 적용시킵니다. 물론 generatedAuditLog 는 순수함수입니다.

마지막으로 그 결과 (Try[String]) 을 대상으로 .foreach 로 돌리는데, 보통 foreach 는 부수효과를 가져옵니다.
val write: String => Unit 를 사용하는것만 봐서도 감이 오는데요. 스칼라(함수형 파라다임)에서는 Unit 을 리턴하는 함수라면 즉 리턴하는게 없는 함수는 대게 부수효과가 있는 함수입니다. 여기서는 write 를 통해서 데이터베이스나 파일에 Sting 을 로그 출력합니다. (부수효과 발생!!) 

여기까지 대략적으로 간단히 살펴보았구요. 나중에 기회가 되면 다른 예제들 및 반응형에 대한 도메인 모델링에 대한 글도 작성하겠습니다.감사합니다.




진짜 함수형 초보자들을 위한 트레이닝(1) in Scala 


검색해보면 많은 초보자들을 위한 함수형 글들이 있지만, 꽤 어렵지 않나 생각이 드는데요. 

예) 초보자친화적 함수형 프로그래밍 투어 <-  이분 글도 좋습니다. 

저도 1년 넘게 회사에서 계속 파이썬만 주력으로 사용해서 감이 많이 떨어져서요. (애초에 감이 있었던적도 없었던..) 이번 글에서는 진짜 쉽게 쉽게 간단한 함수를 사용하는 예제를 통해 감을 익히는 시간을 갖도록 하겠습니다. 덤으로 제네릭을 버무려서 말이죠. 스칼라(Scala) 언어로 할 건데 중간 중간 설명해 드릴테니 몰라도 상관없지 않나 합니다. 그래도 자바,C++,파이썬 같은 언어 경험은 있어야 하긴 할 겁니다.

함수를 매개변수로 넣는것은 C에서도 가능하며 대부분의 언어에서 문제는 없습니다만, 함수형에서는 왜 특별 할까요? 이유는 순수함수이기 때문입니다. 즉 매개변수로 들어가는 함수가 내부에서 어떤 부작용을 일으킬지 걱정을 전혀 안해도 된다는 겁니다. 그 함수가 어떤 값을 리턴하는지만 관심을 집중하면 되거든요. 

즉 함수를 통한 구성을 하는데 있어서 많은 지식과 불안요소가 삭제되며, 아주 큰 시스템이라도 부분 부분을 명확히 이해 가능하기 때문에, 코딩을 하고, 유지보수를 하는데 있어서 심플해 집니다.

먼저 준비운동

def doSomething[A](...): A = 
...
// A 라는 타입의 값(객체)을 리턴하는 겁니다.

def doSomething[A,B,C](...): B => C = 
...
// B => C 함수 객체를 리턴하는 겁니다. (B가 매개변수 C가 리턴값) 

def doSomething[A,B,C](...): A => (B => C) = 
... 
// A 매개변수를 받아서 (B => C) 함수 객체를 리턴하는 함수 객체를 리턴합니다.. (B가 매개변수 C가 리턴값)

이제 본격적으로 시작해보죠!!

예1)

def add (a : Int, b : Int): Int = {
a + b
}

스칼라에서 함수는 def 로 시작합니다. 
a : Int 는 매개변수 a 는 Int 타입이라는 의미이구요. 자바와는 순서가 다릅니다. 
리턴 값은 Int 형이라는 뜻이며 
= { } 에 함수의 본문을 넣습니다. 
a+b 를 리턴하네요. 스칼라에서는 return 을 보통 생략합니다.

add(1,2)

이렇게 호출해주면 3이 나올 겁니다.

def add (a : Int, b : Int): Int = a + b

참고로 이렇게 {} 를 생략 할 수도 있습니다. 

def abs(n: Int): Int =
if (n < 0) -n
else n

절대값을 리턴해주는 함수입니다. return 문은 역시 없죠. if 문을 통해 통째로 평가되어진 값이 리턴되니까요.

예2)

예1에서 add 함수는 Int 형 밖에 사용을 못했는데요. Double 형도 같이 사용하고 싶을 때 어떻하죠? 
네 이때 제네릭(타입 파라미터) 이 사용됩니다. Int 라고 못박지 말고, 그냥 A 라고 해 두는거죠. (사실 aaaa 라고 해도 됩니다만 제네릭에서는 보통 T,A,B,C 이렇게 짧게 사용합니다.) 

def add[T](x: T, y: T): T = x + y

자 이렇게 해주는것이 가장 먼저 떠올랐습니다. 그냥 T 라는 타입으로 대표해서, T 에는 Int, Double 아무거나 포함한다는 것 이겠네요? 뭐 이렇게 해서 add(1,2) or add(5.5, 4,3) 해 보면? 

삑!!!! 이 형태는 오류이며  컴파일이 안됩니다. 저 T 라는 미지의 타입이 도대체 + 라는 메소드 (스칼라에서는 모든것이 메소드이며 1,2,3 같은 숫자가 모두 객체입니다. 즉 1.+(2) 처럼 1 객체는 + 메소드를 호출하는 것) 즉 저 T 타입은 + 라는 메소드를 가지고 있는 숫자처럼 행동하는 무엇인가로 한정지어져야 하는데 그렇지 않다는 건데요. 이것은 스칼라의 특성이므로 (이것을 해결 하는 방법은 글 마지막에 추가 해 놓았습니다만지금 단계에서는 알 필요 없구요. ) 일단은 그냥 저렇게 해도 된다고 생각하고 넘어갑니다. 

def square(x:Int):Int = x * x

def square[T <: Number](x : T):T = { x * x }

이렇게 해도 마찬가지로 에러가 납니다. 

예3) 

def doSomthing (a :Int, f : SomeFunction ) : Int = {
...
}

이번에는 두번째 매개변수로 어떤 함수가 들어왔습니다. (슈도코드입니다) 함수 내부에는 어떻게 구현 될 까요?

def doSomthing (a :Int, f : () => Int ) : Int = {
a + f()
} doSomthing(1, ()=>3) // 이렇게 호출 !! 결과는 4

f 함수가 매개변수 없이 작동하는 함수라면 이렇게 구현 될 수도 있을테고~ 뭘 지지고 볶고 하든 리턴으로 Int 형만 내보내면 됩니다. 
여기서 () => 3 는 스칼라에서 사용하는 함수리터럴(람다식)입니다.  매개변수가 없고  => 오른쪽은 함수 본문으로 그냥 3을 리턴한다는 뜻입니다.

def doSomthing (a :Int, f : Int => Int ) : Int = {
f(a)
} doSomthing(3, (x)=>x*x) // 이렇게 호출 !! 결과는 9

f 함수가 매개변수를 하나 넣어야 작동하는 함수라면 이렇게 구현 될 수도 있을겁니다. 역시 Int 형으로 리턴하기만 하면 됩니다. 여기서 (x) => x*x 매개변수 하나를 받아서 제곱해서 리턴한다는 함수리터럴이죠.  

def doSomthing (a :Int, f : Int => Int ) : Int = {
a + f(10)
} doSomthing(3, (x)=>x*x) // 이렇게 호출 !! 결과는 103


물론 이렇게 구현 될 수도 있습니다. 

다시 반복해 말하지만 중요한것은 함수의 파라미터로 어떤 함수가 들어 갈 수도 있다는 것과, 그 함수가 어떤 타입을 매개변수로 받고 리턴받는지에 대해서만 좀 신경 쓰면 된다는 겁니다. 그 함수는 항상 동일한 파라미터를 넣어주면 동일한 값을 내뱉어주며, 내부에서 어떤 변경을 가해서 멀티쓰레딩에 위험요소를 가져올수 있는 어떤 부작용도 일으키지 않는다고 생각하면 함수를 엮어서 사용하는데에 좀 더 자신감과 명백함을 가질 수 있게 되겠지요. 

예4) 

def doSomthing (a :Int, f : Int => Int ) : Int => Int = { x => 
f(x * a * 100)
}

함수객체는 매개변수로만 들어오는것이 아니라 리턴으로도 사용됩니다.
여기서는 Int 를 매개변수로 받고 Int 를 리턴해주는 함수를 리턴해 주고 있습니다. 

(Int => Int) 에 해당하는 x => f(x * a * 100) //매개변수 하나를 받는 함수리터럴을 리턴해줍니다.

매개변수로 받은 f 함수가 Int 를 리턴해주는 함수라는것에 집중해 주세요. 


val func1 = doSomthing(1, (x)=>x*x) //매개변수 하나를 입력받고 Int 형을 리턴하는 함수를 얻음
println(func1(3)) // 답은 90000

이렇게 사용 할 수 있겠네요.

예5) 

def factorial(n: Int): Int = {

def go(n: Int, acc: Int): Int =
if (n <= 0) acc
else go(n-1, n*acc)

go(n, 1)
} factorial(3) // 답은 6 (3 * 2 * 1)

꼬리재귀함수입니다. 잘보면 go 함수를 내부에서 재귀 호출해주는데 가장 마지막에 호출합니다. 그래서 꼬리재귀입니다. somthing * go (...) 이런식으로 되어있다면 꼬리재귀가 아닌데요. 여기서 마지막에 호출되는것은 * 곱하기이니까요. 

def factorial2(n: Int): Int = {
var acc = 1
var i = n
while (i > 0) { acc *= i; i -= 1 }
acc
}

재귀 함수를 안쓰고 while 문을 쓴다면 위와 같을 겁니다. 이렇게 하면 상태를 변화시키는 var 즉 변수가 사용되네요.

예6)

def findFirst[A](as: Array[A], p: A => Boolean): Int = {

def loop(n: Int): Int =
if (n >= as.length) -1
else if (p(as(n))) n
else loop(n + 1)

loop(0)
}
findFirst(Array(1,10,100), (x: Int) => x == 10)  // 답은 1 

배열에서 10 과 같은 요소의 인덱스를 리턴해 주는 함수입니다. 역시 꼬리재귀를 사용했구요. 순수함수입니다. 특히 눈여겨 볼것은 2가지 인데요.

첫째) 

두번째 매개변수의 시그니처가 p: A =>Boolean 이죠? 즉 A 타입을 인자로 받아서 Boolean 타입을 넘겨주는 함수를 인자로 받겠다는 겁니다. 이 조건에 충족한 함수리터럴(익명함수) 을 넘겼습니다. 
(x: Int) => x == 10  말이죠.  x == 10 은 Boolean 타입으로 평가됩니다. 

둘째) 

배열의 타입을 강제 하지 않았네요. 타입파라미터 A 를 사용했습니다. 따라서 

findFirst(Array("john","park","carry"), (x: String) => x == "park")

문자열의 배열에도 사용 할 수 있게 됩니다.

예7)

def partial[A,B,C](a: A, f: (A,B) => C): B => C =
(b: B) => f(a, b)

마지막 예제입니다. 조금 복잡해 보입니다. 타입파라미터가 3종류이고 (A,B,C) 
매개변수는 2개입니다. 하나는 A 타입이고, 하나는 C 타입을 리턴해주는 함수타입 입니다) 
리턴되는 것 역시 함수타입입니다. B 를 받아서 C 를 리턴 해 주는거 네요. 

함수 본문에서 

 (b: B) => f(a, b)

다음과 같이 B타입을 매개변수로 넣고 f(a,b) 를 통해 C 타입이 리턴되므로, 

: B => C 

이렇게 리턴되는것에 충족됩니다. 잠시 머리속으로 굴려보는 시간을 갖겠습니다. 생각해보세요. 
새로운 파라다임을 공부하기 위해서는 반드시 익숙해 져야하므로 반복,반복 밖에 답이 없습니다. 
여기서 partial 이라는 함수는 a 값이 이미 내장된 또다른 함수를 제공해 주는 것이 목적일 겁니다.

비슷한 것으로 curry 가 있습니다.

def curry[A,B,C](f: (A, B) => C): A => (B => C) =
a => b => f(a, b)

어떤 함수객체를 받아서 부분함수를 만들 수 있는 함수 객체를 리턴 해 주는 군요.
즉 curry 를 실행 한후에 리턴 되는 함수 a => (b => f(a,b)) 
를 이용해서, 원래의 f: (A,B) => C 함수는 A,B 를 둘다 넣어 줘야 했지만, 하나 즉 A 만 넣어 주고 활용 할 수 있게 됩니다.


부록 :

해당내용을 시작하는 시점에 연구할 필요는 없을거 같습니다. (C++개발자들의 대부분이 템플릿메타프로그래밍이나 템플릿 그 자체도 깊이있게 아는사람이 거의 없듯이.. 뭐 그래도 C++ 개발자잖아요?) 

def square[T <: Number](x : T):T = { x * x }

def add[A](x: A, y: A): A = x + y

자 스칼라에서 이렇게는 컴파일이 안되며 (클래스가 아닌 자바의 primitive로 스칼라의 기본 numeric 타입이 매핑되는데, 즉 자바와의 호환성을 유지시켜주기 위해 어쩔 수 없다고도 하는데, 키 포인트는 스칼라에서는 모든 수가 공유하는 공통의 부모타입이 없으며, 대신 수학 라이브러리에서 지원하는 타입 T 에 대한 목시적인 Numeric[T] 가 있다.) 이것을 해결하기 위한 방법은 아래와 같습니다. 타입클래스 패턴이라고도 하는데요.

Numeric[T] 를 사용하면 아래와 같이 간단히 해결되며,  

def square[T](x: T)(implicit num: Numeric[T]): T = {
import num._
x * x
}

def add[A](x: A, y: A)(implicit num: Numeric[A]): A = {
import num._
x + y
}

직접 구현 해서 해결하는 방법은 아래와 같다.

trait Addable[A] {
def plus(x: A, y: A): A
}

implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) {
def +(rhs: A): A = ev.plus(lhs, rhs)
}


implicit object IntIsAddable extends Addable[Int] {
def plus(x: Int, y: Int): Int = x + y
}


implicit object DoubleIsAddable extends Addable[Double] {
def plus(x: Double, y: Double): Double = x + y
}
def add[A: Addable](x: A, y: A): A = x + y

add(5.4,4.3) or add(2,3)



레퍼런스:

Functional Programming in Scala 
https://typelevel.org/blog/2013/07/07/generic-numeric-programming.html
https://twitter.github.io/scala_school/advanced-types.html





스칼라 강의 (47)  ADT (Algebraic Data Types) 이란? (feat. 타입클래스)

"ADT는 정해진 숫자의 타입 군을 구축하는 방법에 관한 이야기. 행동은 상관없다"
"타입클래스는 타입별로 행동을 다르게 하기 위한 이야기."

라고 개인적으론 희미하게 구분하고 있습니다. 참고로 타입클래스는 우리가 알고 있는 그 클래스가 아닙니다. 인터페이스와 비슷한 것으로서 하스켈에서 나온 개념입니다. =>  타입클래스 

죄송하지만 하스켈을 공부해서 ADT에 대한 감을 잡는게 빠른거 같습니다.
(아래 하스켈의 Sum 타입은 자바의 예를들고 있으므로 하스켈 몰라도 이해하기 쉬울 겁니다) 

하스켈의 Sum 타입
- [하스켈 기초][CIS194] 대수적 데이터 타입(Algebraic Data Type)

스칼라에서는 대략 trait 와 케이스 클래스로 ADT 를 정의 하는데 , 아래 예를 보겠습니다.
스칼라는 아시다시피 하이브리드 함수형 언어라, 직접적(간략히)으로 만들 순 없고 먼가 OO틱하게 만들고 있습니다.

Option

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

Either

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]

Try


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

List


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] = ...

다른곳에서 함부러 타입을 추가하지 못하게 제한을 하는 거 봐서 (sealed)  먼가 타입을 만드는데 있어서, 구조화 하고 제한 된 갯수의 타입을 만드는것 같습니다. (합(or) 혹은 곱(and)만큼) 정확한 타입은 패턴매칭을 통해서 분류 가능하고~
이런 ADT 를 이용해서 함수형 프로그래밍을 위한 복합 구성을 하는 것이고~

ADT 와 타입클래스는 어떻게 다른가요?  - 스택오버플로우 발췌

ADT (Algebraic Data Types)와 타입 클래스는 서로 다른 문제를 해결하는 완전히 다른 개념입니다.

ADT

다음과 같이 ADT는 말 그대로 대수적 데이터 타입의 약자이구요. ADT는 데이터를 구조화하는 데 필요합니다. 스칼라에서 가장 근접한 것은 내가 생각하기에, 케이스 클래스와  sealed traits 의 조합인데 이것은 Haskell에서 도 복잡한 데이터 구조를 구성하는 주요 수단입니다. ADT의 가장 유명한 하스켈 예가 아래에 있습니다.

data Maybe a = Nothing | Just a

이 타입은 Option이라고하는 표준 스칼라 라이브러리에 직접적으로 반영 됩니다.

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

이것은 Option이 표준 라이브러리에서 정의 된 방법과 정확히 같지 않지만 포인트를 집어줍니다.기본적으로 ADT는 몇 가지 명명 된 튜플 (0-ary, Nothing / None, 1-ary, Just a / Some (value))의 조합입니다.

다음 데이터 타입을 보세요.

-- Haskell
data Tree a = Leaf | Branch a (Tree a) (Tree a)
// Scala
sealed trait Tree[+T]
case object Leaf extends Tree[Nothing]
case class Branch[T](value: T, left: Tree[T], right: Tree[T]) extends Tree[T]

이것은 간단한 이진 트리입니다. 이 두 정의는 기본적으로 다음과 같이 이해 할 수 있는데요. "이진 트리는 리프 또는 분기 중 하나이며, 분기인 경우 어떤 값과 두 개의 다른 트리가 포함됩니다". 이것이 의미하는 바는 Tree 타입의 변수를 가지고 있다면 Leaf 나 Branch 중 하나를 포함 할 수 있으며, 필요하다면 어느 것이 있는지 확인하고 포함 된 데이터를 추출 할 수 있다는 겁니다. 이러한 검사와 추출은 패턴매칭 통해서 수행합니다.

-- Haskell
showTree :: (Show a) => Tree a -> String
showTree tree = case tree of
  Leaf                    -> "a leaf"
  Branch value left right -> "a branch with value " ++ show value ++ 
                             ", left subtree (" ++ showTree left ++ ")" ++
                             ", right subtree (" ++ showTree right ++ ")"
// Scala
def showTree[T](tree: Tree[T]) = tree match {
  case Leaf => "a leaf"
  case Branch(value, left, right) => s"a branch with value $value, " +
                                     s"left subtree (${showTree(left)}), " +
                                     s"right subtree (${showTree(right)})"
}

이 개념은 매우 간단하지만 매우 강력한데요.

보시다시피 ADT는 닫힙니다. 즉, 타입을 정의한 후에 더 많은 명명된 튜플을 추가 할 수 없습니다. Haskell에서 이것은 구문적으로 시행되며, Scala에서는 다른 파일에서 하위 클래스를  정의하는 것을 허용하지 않는 sealed keyword를 통해 이 작업을 수행합니다.

그러한 이유로 이러한 타입을 algebraic 이라고합니다. 명명된 튜플은 합계(수학적 의미로도)로서 이러한 튜플의 곱 (수학적 의미에서) 및 '조합'으로 간주 될 수 있으며 이러한 생각은 심오한 이론적 의미를 갖습니다.

 For example, aforementioned binary tree type can be written like this:

Tree a = 1 + a * (Tree a) * (Tree a)

But I think this is out of scope for this question. I can search for some links if you want to know more.


타입클래스 

Type classes, on the other hand, are a way to define polymorphic behavior. Roughly type classes are contracts which certain type provides. For example, you know that your value x satisfies a contract which defines some action. Then you can call that method, and actual implementation of that contract is then chosen automatically.

Usually type classes are compared with Java interfaces, for example:

-- Haskell
class Show a where
    show :: a -> String
// Java
public interface Show {
    String show();
}
// Scala
trait Show {
  def show: String
}

Using this comparison, instances of type classes match with implementation of interfaces:

-- Haskell
data AB = A | B

instance Show AB where
  show A = "A"
  show B = "B"
// Scala
sealed trait AB extends Show
case object A extends AB {
  val show = "A"
}
case object B extends AB {
  val show = "B"
}

There are very important differences between interfaces and type classes. First, you can write custom type class and make any type an instance of it:

class MyShow a where
  myShow :: a -> String

instance MyShow Int where 
  myShow x = ...

But you cannot do such thing with interfaces, that is, you cannot make an existing class implement your interface. This feature, as you also have noticed, means that type classes are open.

This ability to add type class instance for existing types is a way to solve expression problem. Java language does not have means for solving it, but Haskell, Scala or Clojure have.

Another difference between type classes and interfaces is that interfaces are polymorphic only on first argument, namely, on implicit this. Type classes are not restricted in this sense. You can define type classes which dispatch even on return value:

class Read a where
  read :: String -> a

It is impossible to do this with interfaces.

Type classes can be emulated in Scala using implicit parameters. This pattern is so useful that in recent Scala versions there is even a special syntax which simplifies its usage. Here is how it is done:

trait Showable[T] {
  def show(value: T): String
}

object ImplicitsDecimal {
  implicit object IntShowable extends Showable[Int] {
    def show(value: Int) = Integer.toString(value)
  }
}

object ImplicitsHexadecimal {
  implicit object IntShowable extends Showable[Int] {
    def show(value: Int) = Integer.toString(value, 16)
  }
}

def showValue[T: Showable](value: T) = implicitly[Showable[T]].show(value)
// Or, equivalently:
// def showValue[T](value: T)(implicit showable: Showable[T]) = showable.show(value)

// Usage
{
  import ImplicitsDecimal._
  println(showValue(10))  // Prints "10"
}
{
  import ImplicitsHexadecimal._
  println(showValue(10))  // Prints "a"
}

Showable[T] trait corresponds to type class, and implicit objects definitions correspond to its instances.

As you can see, type classes are a kind of interface, but more powerful. You can even select different implementations of type classes, while the code which uses them remains the same. This power, however, comes at the cost of boilerplate and extra entities.

Note that it is possible to write Haskell equivalent of above Scala program, but it will require writing multiple modules or newtype wrappers, so I'm not presenting it here.

BTW, Clojure, a Lisp dialect working on JVM, has protocols, which combine interfaces and type classes. Protocols are dispatched on single first argument, but you can implement a protocol for any existing type.





아주 비슷한 느낌을 주는 두 언어 스칼라와 코틀린에 대한 비교 글이 있어서 번역해 보았습니다
이런 글을 읽고 이 언어가 무엇인지 정확히 감을 잡는것은 불가능합니다. 가볍게 읽으세요. 또한 이 글은 superkotline.com 이라는 코틀린 싸이트의 글이므로 약간은 코틀린쪽으로 기운 느낌도 있습니다. 
저는 스칼라와 Python 을 좋아하기 때문에 먼저 다음과 같은 언급을 하고 시작해 봅니다. :-) 


"Twitter 인프라의 대부분은 Scala로 작성되어 있으며..."




Scala vs Kotlin : 네가 해결하기 원하는 문제(분야)가 뭐야? 


Scala와 Kotlin은 자바 왕좌( JVM 기반) 를 차지하기 위한 주요 두 경쟁자입니다.

그들은 같은 문제를 해결하는 것을 목표로합니다 : 더 나은 자바를 제공
하지만 그들은 매우 다른 방식으로 달려가는데요. 

  1. 스칼라는 아카데미에서 설계되었으며, Kotlin은 선도적인 소프트웨어 회사에서 출발 했습니다. 그들이 태어난 배경이 그 차이점을 잘 설명 해 주고 있는데요. Scala는 함수형 프로그래밍과 다른 패러다임 간의 하이브리드라는 멋진 아이디어를 실현하기 위해 탄생했습니다. Kotlin은 실용적인 문제를 해결하려고합니다. Kotlin 디자이너는 컴파일 시간과 훌륭한 도구 지원에 특히 신경을 썼구요..그것이 다른 언어 디자이너에게는 흥미 진진하지 않더라도 프로그래머에게는 그런 인프라가 매우 중요하기 때문이죠.
  2. 스칼라의 큰 장점은 JVM을 위한 또 하나의 주요 정적 언어를 만드는 것이 가능하다는 것이 입증되었다는 것입니다. JVM은 언어 디자이너가 융통성있게 사용할 수 있을만큼 유연했음을 분명히했습니다. Kotlin은 나중에 작업이 시작되었구요. 이제 사람들은 새로운 JVM 언어를 사용한다는 생각에 흥분하기 보다는 그것의 실용적인 측면에 관심을 갖기 시작했습니다.

이 기사에서는 Kotlin과 Scala를 기술적 모양(문법)이 아닌 용도로 주로 비교할 것입니다. (역주: 모양 비교는 이 글 끝에 추가하였습니다. ) 우리는 이 두 언어의 기능에 대해 개괄적으로 살펴보고자하며, 주로 설계된 것에 집중하고 있습니다.


자바는 뭐가 별론데?

Kotlin과 Scala 모두 JVM에서 실행되며 Java와 비교됩니다. 이것은 질문을 유도하게 되는데요.

Java에 무슨 문제가 있습니까?

음, 확실히 Java는 많은 일을 올바르게 처리했습니다. 그것은 혁명적인 언어라기보다 진화적인 언어였습니다. (역주: 진화가 c#에 비해 비교적 더디었다는것은 사실이며, 진화가 빨라야 좋냐? 라면 그것은 케바케겠죠) 자바는 개발자들에 의해 편하게 사용 가능하고, (가비지컬렉팅등) 쉽게 이해 가능하도록 만들어 졌을 뿐 만 아니라, (C++의 ㅣ군더더기 제거및 OOP 를 잘 사용하게 유도)  매우 많은 것을 제공 (모든것이 있는 라이브러리 군단들) 했습니다. 또한 Write once, run anywhere 는 매력적이었습니다.

사실 Java는 아마도 세계에서 가장 많이 사용되는 프로그래밍 언어 일 것입니다. Java는 또한 엔터프라이즈 세계에서 가장 많이 사용되는 언어이며 컴퓨터 과학 과정에서 가장 널리 사용되는 언어입니다.


그래 자바는 위대하지. C처럼. 하지만 C 처럼 자바도 늙었다네..


Java가 C보다 발전했으며 운 좋게도 C++보다 체계적인 방식으로 개발되었지만 , 현재는 다른 많은 새로운 언어에 비해 뒤떨어져 있습니다. 그래도  아직까진 많은 개발자들이 Java에 만족합니다. 그러나 많은 사람들이 비슷한 목적을 가진 언어인 C #과 비교해 보았습니다. 또는 다른 거의 모든 현대 언어.

많은 개발자와 회사가 더 생산적인 것을 원한다는 것은 비밀이 아닙니다. Java 자체에 대한 많은 기술적 비판과 더 사용하기 쉽게 하기 위한 방식을 제한하는 많은 걸림돌이 있습니다. 이것은 성공의 저주지요. 많은 사람들이 이미 그것을 사용하고 있기 때문에 설계자는 자신이 뭔가 잘못되었다는 것을 알았더라도 근본을 바꿀 수 없습니다.(역주: 하스켈언어는 스스로 성공하지 않기를 바라고 있습니다. Avoid success at all costs

왜 Scala 와 Kotlin?






수년 동안 개발자들은 JVM에 새로운 언어를 구축해 왔으며 이러한 언어 중 상당수는 Java와 비교하여 나름 개선된 기능과 Java 이상의 기능을 제공합니다. 많은 사람들이 개발자의 생산성과 경험을 향상 시키려고 노력했으며, Java로 할 수있는 것과 동일한 일을 수행하거나 Lisp과 비슷한 동적 언어 같은 다른 것을 만들려고했습니다.

Scala는 사용자의 요구에 따라 확장 가능한 언어인 Scalable Language의 약자이며 Kotlin은 Java와 마찬가지로 섬 (러시아)의 이름입니다.

우리는 Kotlin과 Scala를 선택했는데 그 이유는 정적타입 언어이며 두 가지 접근법의 훌륭한 대표자이기 때문입니다. Kotlin은 의도적으로 Java보다 개선된 언어를 지향하며, Scala는 철학이 다른 언어입니다. Kotlin은 "필요한" 모든 것을 제공하며, Scala는 "원하는" 모든 것을 제공합니다.

스칼라에는 훌륭한 기능들이 있지만 가장 명백한 결함은 컴파일 속도가 매우 느리다는 것입니다.

Scala에서 Kotlin으로 옮기기로 결정한 사람들의 기사를 이미 찾을 수 있습니다. 어떤 면에서 Kotlin의 창작자도 그렇게 한거죠.  Kotlin 을 창안하기로 결정한 이유 중 하나를 설명한 Kotlin 개발자와의 인터뷰 에서 알수 있습니다.

물론 Kotlin이 Scala보다 낫다는 것은 아닙니다. 반대로 Kotlin 디자이너는 스칼라가 훌륭한 언어라고 생각합니다. 하지만 자바와 마찬가지로 스칼라도 자신들의 필요에 맞게 설계되지 않았기 때문에 코틀린을 만든 것이죠.

몇 가지 기능 비교

우리가 소개에서 말했듯이, 우리는 모든 기술적 인 특징을 열거하고 있지도 않고 문법을 보고 있지도 않습니다. 너무 많은 그런 세부 정보는 문맥없이 바라볼때  혼동이 될 수 있다고 생각합니다. 그럼에도 불구하고 몇가지 중요한 특징과 언어가 선택하는 접근 방식을 살펴 볼 필요는 있는거 같습니다.

객체 지향

Java는 객체 지향 프로그래밍을 위해 설계된 언어이지만 모든 것이 객체는 아닙니다. 숫자와 같은 기본 유형은 primitive 타입으로 표시됩니다.반면에 스칼라와 코틀린에서 모든 것은 객체이며 그 자체로 접근 할 수 있습니다. primitive  타입 대신에 참조 타입을 사용하는 것에 약간의 불이익이 있기 때문에 이것이 아픈 선택이긴 하지만, JVM에서는 값 타입을 정의 할 수 없다는 사실 때문입니다. 따라서 더 똑똑한 타입 시스템의 장점을 얻으려면 참조 유형으로만 구현하면됩니다.

Scala와 Kotlin은 다른 프로그래밍 패러다임도 지원합니다. Kotlin은 주로 객체 지향 언어로 무게추가 남아 있지만 일반적인 프로그래밍에서 유리하게 작동 할 수 있는 모든 프로그래밍 패러다임의 기본 요소를 포함합니다.

함수형 프로그래밍

Kotlin은 람다 (lambdas) 및 고차원 함수와 같은 기본적인 기능을 지원하지만 함수형 프로그래밍 패러다임을 완전히 지원하지는 않습니다. 언제나 그렇듯이, 컴퓨터 과학에서 박사 학위를 요구하지 않고도 평균 프로그래머에게 유용 할 수있는 실용적인 해법만 제공합니다. 예) 

  • 독립적 (최상위) 함수가 좋으며, 별도의 함수를 포함하기 위해 유틸리티 클래스를 만들 필요가 없습니다.
  • Kotlin은 문장이 아닌 표현식을 정의하므로 if 의 결과 같은 것을 변수에 할당 할 수 있습니다.
  • val 을 이용하여 불변의 데이터를 쉽게 정의 할 수 있습니다 final 같은 수정자가 필요 없음

Scala는 함수 및 객체 지향 프로그래밍 패러다임을 동일하게 지원합니다 . 이 목표를 염두에 두고 설계되었으며, 하스켈에서 찾을 수 있는 대부분의 기능을 갖추고 있습니다. Kotlin이나 Java에서는 currying, memoization, partial application, type classes등의 기능이 없지만 스칼라는 모두 가지고 있습니다.

작은 것들

모든 언어가 다를 수 있습니다. Kotlin과 Scala는 생산성을 높히는 많은 기능들을 가지고 있습니다. Java가 만들어 질 때  없었던 타입 추론과 싱글톤 또는 데이터 클래스를 만드는 빠른 방법과 같은 것들 말이죠.

코틀린

Kotlin은 null-safety 와 smart casts 와 같은 흥미로운 것들에 대한 지원을 제공하고 있습니다. smart casts 란 새로운 유형으로 자동 캐스팅되는 변수의 타입을 확인한 경우 해당 변수가 실제로 해당 타입의 변수인지 여부를 확인하는 것을 의미합니다. 일반적으로 Kotlin은 더 많은 기능을 추가하는 대신 기능 집합을 매우 잘 사용합니다. 예를 들어, 연산자를 오버로드 할 수 있지만 이미 언어에 있는 기본 연산자만 가능하며 새 연산자를 정의 할 수는 없습니다.

Kotlin은 내부 DSL (역주: 미니언어 ) 이나 유창한 인터페이스를 쉽게 만들 수 있습니다. 그것은 람다와 구문 도우미(syntactic sugar) 의 도움으로 그렇게 합니다. 사실 함수의 마지막 인자가 람다라면 괄호 밖에서도 사용할 수 있습니다. 이것은 복잡한 문자열 보간의 필요성을 줄이는데 유용합니다. 예를 들어,이 예제는 HTML을 생성하기 위해 라이브러리를 사용합니다.

createHTML().table {
    for (language in languages) {
       tr {
           td { +language.name }
           td { +language.popularity }
       }
    }
}

스칼라

스칼라는 함수형으로나 객체 지향적으로 원하는 모든 기능을 제공합니다. 그것에는 약간의 약점도 있지만 (예를들어 null-safety의 managament는 Kotlin보다 덜 효율적이며 자연스럽지 않습니다.) 스칼라는 매크로를 포함하고 복잡한 연산자를 생성 할 수 있기 때문에 복잡한 내부 DSL을 만드는 데에 탁월합니다. 스칼라를 사용하면 큰 자유와 수많은 기회를 가질 수 있습니다. 하지만 그 자유 때문에 몇몇 개발자는 코드 자체에서 목적이 명확하지 않은 이상한 사용자 지정 연산자를 과용하는 것에 힘들어 할 수도 있습니다.


Java와의 상호 운용성

함께 일하는 사람들

Kotlin은 Java와 함께 작동하도록 설계되었습니다.

Kotlin은 Java와의 호환성을 유지하기 위해 만든 언어입니다. 심지어 Java에서 Kotlin 관용 코드를 쉽게 호출 할 수 있도록 Java와의 상호 운용성을 용이하게하는 기능도 있습니다. 따라서 기존 소프트웨어를 Java로 계속 개발할 수 있고,  Kotlin으로 새로운 소프트웨어를 만들 수 있습니다.

 Kotlin이 Android에서 매우 인기가 있는 큰 이유 중 하나는 주로 Android 6의 Java 버전인 Java 6과의 호환성 때문입니다. 따라서 Kotlin은 이미 Java 8에있는 기능을 제공하지만 Android 개발자에게는 제공되지 않습니다.

Java와의 상호 운용성은 우연이 아니며 단순히 JVM에서 실행되는 언어를 사용하여 얻을 수 있습니다. JetBrains는 Kotlin이 현재 Java 생태계와 상황을 인식하도록 설계했습니다. 그들은 사용하기 쉬운 방식으로 최대한의 가치를 이끌어 내려고 노력했습니다. 이 호환성은 바이트 코드 자체의 구조에 적용됩니다. 코다 (Corda)가 스칼라 (Scala)보다 코틀린 (Kotlin)을 선택하게 된 이유 중 하나는 바로 그 때문입니다 .

스칼라는 자바와 작업 가능 하다.

Scala는 보다 쉽게 ​​배포 할 수 있도록 JVM 플랫폼에서 실행되도록 만들었지만 호환성이 실제로 주요 목표는 아닙니다. 예를 들어 최근 버전의 스칼라에는 Java 8이 필요합니다. 

언어의 특징과 강하게 결합되었다는것의 의미는 스칼라로 자바를 사용하여 개발하기를 원한다면 기존 Java 코드베이스로 작업 할 수 있지만 잘 살펴봐야합니다. Scala를 옆에 끼고 Java 소프트웨어를 개발하고 있다구요? 별로 좋은 생각이 아닙니다.

스칼라가 객체 지향 프로그래밍뿐 아니라 함수형 프로그래밍을 지원한다는 사실 때문에 호환성이 쉽지만은 않습니다. 고급 기능을 사용하는 경우 자바에서 그다지 많은 기능을 수행 할 수 없습니다. 가능한 경우에도 Java 프로그래머는 이를 사용하는 방법을 모를 수도 있습니다.

(역주: 스칼라를 선택한 이유중 한가지로 광활한 자바 라이브러리의 사용을 꼽을 수 있다. 스칼라에서 그런 자바 라이브러리를 가져다 사용하는것은 그닥 문제가 되지 않는다.) 

다른 철학

많은 성공적인 언어는 정확한 목표와 프로그래밍이 무엇인지에 대한 구체적인 철학을 가지고 있습니다. Kotlin과 Scala도 마찬가지입니다. 스칼라는 자바보다 훨씬 더 많은 것을 계획하고 있지만 (역주: JVM 을 탈피하여 컴파일 언어가 되는 작업도 진행중) 코틀린은 더 나은 자바가 되고 싶어 합니다.

Kotlin은 더 나은 자바인가?

Kotlin은 원래 목표를 달성했다고 주장 할 수 있습니다. Java가 아닌 Android 개발을 위해 공식적으로 지원되는 첫 번째 언어가 되었습니다. 이는 자바 공동체가 Kotlin을 좋아하고 Java 개발자에게 Kotlin을 소개하는 것이 쉽다는 것을 입증하는 위대한 업적입니다.

Kotlin은 생산성을 향상시키고 Java 개발자에게 배우기 쉽도록 만들어진 실용적인 언어입니다. 주로 Java와 비슷하게 디자인 되었지만 Java 모델 위에서 C# 및 Scala 의 좋은 모습 또한 포함되어 있습니다. 람다 및 기본 기능과 같은 자바 프로그래머가 원하는 기능을 추가하고 스마트 캐스팅 및 null이 허용되지 않는 타입과 같은 것을 통해 개발자 생활을 단순화 시킵니다. 많은 경우에 코틀린 에디션은 보통 Syntactic sugar 라고 불리는 그것입니다 개발자가 사용하는 일반적인 패턴을 표현하는보다 간결한 방법을 제공한다는 것이죠.

Kotlin은 함수형 프로그래밍에 대한 일부의 지원을 제공하지만 보다 쉬운 절차 또는 명령적 프로그래밍을 수행하기 위해 위한 것입니다. 예를 들어, 개별 함수가 이제는 언어의 first class 시민이 될 수 있습니다. 코틀린은 formal 하기 보다는 단순한 것을 선호합니다.

스칼라는 Java보다 강력합니다.

스칼라는 그 일( 더 나은 자바) 를 시도하기 보다는 Java보다 강력하도록 설계되었습니다.  일반적으로 자바보다 더 나은 언어입니다. Scala는 Java가 할 수없는 일을 하도록 설계되었습니다.

스칼라는 고급 함수 프로그래밍을 훌륭하게 지원합니다. 사실 함수 프로그래밍은 객체 지향 프로그래밍과 마찬가지로 first-class citizen 입니다. 하지만 이로 인해 복잡성이 증가하고 배우고 사용하기 좀 어렵기로 유명한 언어로 만듭니다. 패러다임을 혼합 할 수 있으므로 유기적으로 혼합한다거나 하기 위해서는 모든 것을 알아야합니다.

이는 어떤 사람들에게 유리하게 작용할 수 있으며, 어떤 사람들에게는 단점이 될 수 있습니다. 스칼라 에는 여러 가지 스타일의 프로그래밍이있어 혼란을 가져 올 여지가 있으며 모든 필요에 따라 최상의 스타일을 사용할 수 있습니다. 어쨌든 이것은 더 높은 개발 비용과 높은 스칼라 개발자를 위한 임금 인상으로 이어집니다. 당신이 누구인지에 따라 좋거나 나쁜 것일 수 있습니다.

(역주: 스칼라도 함수형 파라다임을 그냥 도우미 정도로만 생각한다면 더 나은 객체지향 자바로써 사용 할 수도 있습니다.) 

언어의 세계

일광과 하프에서 지구의 절반을 표현한 것

커뮤니티

 Kotlin이 아마도 이미 우위를 차지하고있는 Android 개발에 대해 이야기하지 않는 한 스칼라는 더 커다란 공동체를 가지고 있습니다..만  Kotlin이 받은 열정 을 보려면 2017 Google I / O를 살펴보십시오 .

네이티브 라이브러리의 상대적 부족은 우수한 Java 호환성으로 인해 다소 완화됩니다. 그러나 idiomatic 한 Kotlin 코드가 있는 강력한 라이브러리는 Kotlin에서 항상 사용할 수있는 것은 아니며 Scala와 Java용 라이브러리 같이 깊이 개발되지는 않았습니다.

문서화 및 학습의 용이함

두 언어 모두 좋은 문서를 가지고 있습니다만 스칼라에서 개발자를 훈련시키고  그것의 개발 스타일로  훈련시키는것이 쉽지만은 않습니다

코틀린은 다릅니다.  Corda는

우수한 문서와 작은 표준 라이브러리는 매우 빠른 언어 학습을 가능하게합니다. 우리는 Kotlin의 경험이 필요하다고 명시적으로 광고하지 않으며, 모르는 사람을 고용하여도 새로운 팀원이 코틀린으로 괜찮은 코드를 생성하기까지 1-3 일의 범위 내 였습니다.

Kotlin은 배우기 쉽고 사용하기 쉽고 예제 가 있는 훌륭한 온라인 테스트 환경을 갖추고 있으며 Java 코드로도 변환 할 수 있습니다. 따라서 Java에서 수행하는 것과 동일한 작업을 수행하는 방법을 즉시 확인 할 수 있습니다.

툴링(Tooling)

스칼라와 코틀린은 모두 정적 형식의 언어이므로 컴파일러 검사 및 정적 분석 도구 전체를 허용합니다. 그래서 잠재력은 비슷합니다. 하지만 프로그래밍 툴의 유명한 소프트웨어인 JetBrains가 만든 Kotlin의 툴링이 좀 더 좋은것은 당연한 것이 겠지요. 문서에 관해서는, Scala가 나쁜 경우는 아니지만 Kotlin은 더욱 더 굉장합니다.
 IntelliJ IDEA 의 스칼라 지원도 빠방합니다만 
 Kotlin은 이미 IntelliJ IDEA와 Android Studio에 통합되어 있습니다. 또한 이클립스용 플러그인도 있구요. 또한 IntelliJ IDEA는 온라인에서 사용 가능한 동일한 변환 도구와 예제를 제공하므로 대화식 예제를 사용하여 Java 클래스를 쉽게 변환하고 언어를 배울 수 있습니다. 

개요

Kotlin 디자인은 전체론적(holistic) 접근 방식을 취하는 것처럼 보입니다. 작성자는 각 디자인 결정이 전체 개발 경험에 어떻게 영향을 주는지 고려했습니다. 그 기능이 정말로 필요 했습니까, 아니면 대안이 있습니까? 그 선택은 컴파일러의 속도를 느리게합니까? 훌륭한 도구 지원을 제공하기가 더 어려워 집니까?

스칼라 디자이너들은 실용주의자들에 의해 괴롭힘을 당하지 않은 것처럼 보입니다. 이것은 마치 C ++을 생각 나게합니다. (역주: 현재 제가 그런 생각을 좀 하고 있습니다. 10여년 전에 Modern C++ design 책을 보며 자괴감이 들었던것처럼 현재 FPIS 를 읽으며 비슷한 느낌이 드는... 그러고 보니 둘다 빨간책이군요 ㅎㅎ) 물론, 그들은 자신의 하위 집합을 채택하여 조직에서 사용되는 언어의 복잡성을 줄일 수 있다고 말합니다. 그것은 회사에서는 작동 할 수 있지만, 모든 사람들을 위한 아이디어는 아닌 거 같습니다.

많은 사람들이 자바 보다 더 나은 언어에 대한 희망으로 스칼라로 옮겼습니다. 첫 번째 동기 부여가 있는 대부분의 사람들은 Scala에 만족합니다. Java에 단순히 만족하지 못하는 많은 사람들은 Scala의 복잡성에 조금 당황합니다. 네!!! 그것은 그들에게 맞는 언어가 아닙니다. 그것은 단순한 오래된 Java와는 다른 무언가입니다.

Kotlin은 아마 그들에게 더 좋은 언어 일 것입니다. 모든 자바 개발자는 스칼라가 아닌 Kotlin을 쉽게 선택할 수 있습니다 .

Java 자체보다 생산성이 높은 자바와 같은 언어가 필요한 경우 Kotlin과 함께해야합니다. Haskell과 Java가 필요하다면 Scala로 가야하지만요..


이 실용적인 비교가 유용했기를 바랍니다. 기술적인 비교와 두 언어의 문법을보고 싶다면  아래 링크를 참고하세요.






번역: https://superkotlin.com/kotlin-vs-scala/


 [경고] 개인적으로 공부한것 정리한 것이라 내용이 두서없이 조악하게 연결되어 있습니다.


스칼라에서 리프팅(lifting) 이란? [번역/요약] 


먼저 스칼라에서 에러처리하는 방식으로 option 을 사용하는데, 사용하다보면 이것을 리턴 받아서 사용 할 때 일일이 확인해서 처리 해주다보면 군더더기 코드가 덕지 덕지 되지 않을까 생각 할 수 있을 것이다. 초보시절에는 if 문이나 case 매칭으로 지져분하게 해결 한 경험도 많을 것인데, 여기서 설명할 리프팅을 알고 나면 그렇게 코딩 했어야 했었던 초보시절의 죄책감을 앞으로는 벗어 던질 수 있을 것이다.


아래 예 (pseudo-code)를 보면

x = someOperation
if !x.nil?
y = someOtherOperation
    if !y.nil?
    doSomething(x,y) return "it worked"
    end
end
return "it failed"

함수를 실행해서 리턴 받은 값이 정상적인지 조사해서, 그 값에 문제가 없을 경우 다음 프로세스를 진행하는 if 로 범벅된 전형적인 코드이다.

아시다시피 자바에서 null은 객체가 아니며 메소드도 가지고 있지 않은데, 정적으로 형식화 된 규칙의 예외라고 볼수 있다. (null은 클래스가 아니지만 클래스에 대한 참조를 null로 설정할 수 있다). null에서 메소드를 호출하면 하나의 결과인 예외가 throw 될 뿐이다. 실제로 null을 발명 한 사람은 10 억 달러의 실수라고 말했다.

Ruby는 null보다 조금 더 나은 nil을가지고 있는데 nil은 실제 싱글톤 객체이다. 즉 전체 시스템에 단 하나의 인스턴스 만 존재하며 메소드도 가지고 있고  Object의 서브 클래스이기도 하다. Object에는 "nil"이라는 메서드가 있으며 이 메서드는 false를 반환하지만 nil 싱글톤은 이 메서드를 재정의하여 true를 반환한다. nil은 Java에서 null과 매우 비슷하게 반환되며,  그것은 "유효한 대답 없음" 이라는 의미이다.

우리의 스칼라는 조금 다른 철학을 가지고 있다.

Option이라고하는 강력한 타입의 추상 클래스가 있으며. Option [T]로 선언 된다. 즉, Option은 모든 타입이 될 수 있지만 일단 타입이 정의되면 변경되지 않는다. Option에는 Some 하위 클래스와 None 하위 클래스가 있으며,  None 은 싱글톤 (nil과 같다) 이고 Some 은 일종의 컨테이너이다. 따라서 다음과 같은 메소드가 있을 수 있다.

def findUser(name: String): Option[User] = {
val query = buildQuery(name)
val resultSet = performQuery(query)
val retVal = if (resultSet.next) Some(createUser(resultSet)) else None
resultSet.close
retVal
}

Some(User) 또는 None을 반환하는 findUser 메서드가 있다. 지금까지는 위의 예제와 많이 다르지 않습니다. 그래서 모든 사람들을 혼란스럽게하기 위해 컬렉션에 대해 잠깐 이야기 할 것입니다.

스칼라에서 정말 좋은 점은 (예, Ruby도 마찬가지) 풍부한 리스트 오퍼레이션들인데, 카운터를 생성하고 리스트(배열) 요소를 하나씩 꺼내는 대신에 작은 함수를 작성하고 해당 함수를 목록에 전달한다. 리스트는 각 요소가 있는 함수를 호출하고 각 호출에서 반환 된 값으로 새 리스트를 반환한다. 아래 코드를 참고하자.

scala> List(1,2,3).map(x => x * 2)
line0: scala.List[scala.Int] = List(2,4,6)

위의 코드는 각 목록 항목에 2를 곱하고 "map"은 결과 목록을 반환한다. 좀 더 간결하게 다음처럼도 가능하다.

scala> List(1,2,3).map(_ * 2)
line2: scala.List[scala.Int] = List(2,4,6)

map 작업을 중첩 할 수 있도 있다.

scala> List(1,2,3).map(x => List(4,5,6).map(y => x * y))
line13: scala.List[scala.List[scala.Int]] = List(List(4,5,6),List(8,10,12),List(12,15,18))

그리고 내부 목록을 "평평하게"할 수도 있다. (flatMap 이용)

scala> List(1,2,3).flatMap(x => List(4,5,6).map(y => x * y))
line14: scala.List[scala.Int] = List(4,5,6,8,10,12,12,15,18)

마지막으로 첫 번째 목록에서 짝수를 "필터링"할 수 있다.

scala> List(1,2,3).filter(_ % 2 == 0). flatMap(x => List(4,5,6).map(y => x * y))
line16: scala.List[scala.Int] = List(8,10,12)

그러나 보시다시피 map / flatMap / filter 항목은 조금 복잡하다고 할 수 있는데, 스칼라는 코드를 보다 쉽게 읽을 수 있도록 "for" comprehension 을 도입했다.  (역주: flatMap 과 for-comprehension 의 유사성에 주목)

scala> for {
x <- List(1,2,3) if x % 2 == 0
y <- List(4,5,6)} yield x * y
res0: List[Int] = List(8, 10, 12)

좋다. 그런데 이것이 Option [T]와 어떤 관련이 있나? 


Option은 map, flatMap 및 filter (스칼라 컴파일러가 for-comprehension 에 사용하는 데 필요한 메서드)를 구현한다. 따라서 이 구조를 매우 효과적으로 사용할 수 있다는 것이다. 첫 번째 예는 간단하다.

scala> for {x <- Some(3); y <- Some(4)} yield x * y
res1: Option[Int] = Some(12)

3과 4 곱하는 엄청난 코드입니다만 여기에 None 이 있으면 어떻게 되는지 봅시다.

scala> val yOpt: Option[Int] = None
yOpt: Option[Int] = None
scala> for {x <- Some(3); y <- yOpt} yield x * y
res3: Option[Int] = None

None 을 되돌려 받았다.

scala> (for {x <- Some(3); y <- yOpt} yield x * y) getOrElse -1
res4: Int = -1
scala> (for {x <- Some(3); y <- Some(4)} yield x * y) getOrElse -1
res5: Int = 12

"getOrElse"코드를 사용하여 None 일 경우 디폴트 값을 돌려 받도록 하였다.


아래와 같은 코드를 이용하여, A,B 타입을 받아서 C 타입을 반환하는 함수를 Option[A] 와 Option[B] 를 받아서 Option[C] 를 받는 함수로 승급(lifting) 시킬 수 있게 된다. 

def lift[A,B] (f: A => B): Option[A] => Option[B] = _ map f 

아래는 math.abs 함수를 승급 시키는 것을 보여준다.

val abs0: Option[Double] => Option[Double] = lift(math.abs) 

두 Option 값을 이항 함수 (binary function) 를 이용해서 결합하는 일반적 함수 map2 이다. 두 Option 값 중 하나라도 None 이면 map2 의 결과 역시 None 이다. 


def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
a flatMap (aa => b map (bb => f(aa, bb)))

Option 들의 목록을 받고, 그 목록에 있는 모든 Some 값으로 구성된 목록을 담은 Option 을 돌려주는 함수는 아래와 같다.  마찬가지로 목록에 None 이 하나라도 있으면 None 이 된다. 

/*
Here's an explicit recursive version:
*/
def sequence[A](a: List[Option[A]]): Option[List[A]] =
a match {
case Nil => Some(Nil)
case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
}
/*
It can also be implemented using `foldRight` and `map2`. The type annotation on `foldRight` is needed here; otherwise Scala wrongly infers the result type of the fold as `Some[Nil.type]` and reports a type error (try it!). This is an unfortunate consequence of Scala using subtyping to encode algebraic data types.
*/
def sequence_1[A](a: List[Option[A]]): Option[List[A]] =
a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _))





아래 글은 Scala에서 리프팅이 무엇이냐라는 물음에 대한 스택오버플로우 대답니다.


Scala 에서 리프팅은 다양한 의미를 갖습니다.

PartialFunction

PartialFunction[A, B] 가 도메인 A 의 서브셋으로 정의된 함수임을 기억하시구요.  "lift" 를 통해서 PartialFunction[A, B] 를  Function[A, Option[B]]로  리프팅 할 수 있을 것입니다. 이것은 PartialFunction 에 있는 lift 메소드를 통해서 명시적으로 완수 될 것입니다. 예를 보시죠. 

scala> val pf: PartialFunction[Int, Boolean] = { case i if i > 0 => i % 2 == 0}
pf: PartialFunction[Int,Boolean] = <function1>

scala> pf.lift
res1: Int => Option[Boolean] = <function1>

scala> res1(-1)
res2: Option[Boolean] = None

scala> res1(1)
res3: Option[Boolean] = Some(false)

PartialFunction 내의 lift 메소드를 통해서 Boolean 타입이 option[Boolean] 으로 바뀌었습니다. 


Methods

메소드를 함수로 "lift" 할 수 있습니다. 이것을 eta-expansion 이라고 하는데요. 예를 보시죠.

scala> def times2(i: Int) = i * 2
times2: (i: Int)Int

underscore 를 applying 하여 메소드를 함수로 리프팅 하였습니다.

scala> val f = times2 _
f: Int => Int = <function1>

scala> f(4)
res0: Int = 8


Functors

펑터(scalaz에 의해 정의된 의미로)는 어떤 "컨테이너" 로 볼수 있는데, F such that, if we have an F[A] and a function A => B, then we can get our hands on an F[B] (think, for example, F = List and the map method)

이 속성은 다음과 같이 인코딩 할 수 있다.

trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}

이것은 A => B 함수를 "functor"의 도메인으로 "lift" 하는 것과 동형입니다. 

def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]

즉, F가 functor이고 우리가 A => B라는 함수를 가지고 있다면, 함수 F [A] => F [B]가 됩니다. 리프트 방법을 시도하고 구현할 수도 있습니다. 


Monad Transformers

As hcoopz says below (and I've just realized that this would have saved me from writing a ton of unnecessary code), the term "lift" also has a meaning within Monad Transformers. Recall that a monad transformers are a way of "stacking" monads on top of each other (monads do not compose).

So for example, suppose you have a function which returns an IO[Stream[A]]. This can be converted to the monad transformer StreamT[IO, A]. Now you may wish to "lift" some other value an IO[B] perhaps to that it is also a StreamT. You could either write this:


StreamT.fromStream(iob map (b => Stream(b)))

Or this:

iob.liftM[StreamT]

this begs the question: why do I want to convert an IO[B] into a StreamT[IO, B]?. The answer would be "to take advantage of composition possibilities". Let's say you have a function f: (A, B) => C


lazy val f: (A, B) => C = ???
val cs =
for {
a <- as //as is a StreamT[IO, A]
b <- bs.liftM[StreamT] //bs was just an IO[B]
}
yield f(a, b)

cs.toStream //is a Stream[IO[C]], cs was a StreamT[IO, C]



번역 레퍼런스:

fpinScala (function programming in scala) 의 4장

https://simply.liftweb.net/index-7.2.html

https://stackoverflow.com/questions/17965059/what-is-lifting-in-scala

 

스칼라 어플리케이션은 어떻게 빌드&디플로이 할까? [번역]

 

 

 

 

 

이봐요!  스칼라에 꽤나 익숙해 진 것 같군요. 당신이 이제 스칼라로 어플리케이션 개발을 할 수 있는것을 알겠습니다. 근데 코드의 주된 사명은 사람들이 문제를 해결 할 수 있도록 돕는 것이겠죠? 하지만 당신의 데스크탑에서만 로컬로 실행중인 경우 어떻게 이 작업을 수행 할 수 있을까요? 네~이 글에서는 Scala 애플리케이션을 배포하는 가장 쉬운 방법을 보여드리겠습니다.

 

스칼라 프로젝트 빌드하기

Scala 프로젝트를 "빌드" 한다고 말하면 무엇을 의미하나요? 그 의미는 당신의 IDE에서 로컬로 실행되는 프로젝트는 다른 환경 (Linux / Windows / Mac)에서 실행될 수 있게 "변형" 되어야합니다.Scala 프로젝트 샘플을 좀 살펴 보겠습니다. 정말 간단해야 겠죠? 그래서 사소한 akka-http 앱을 만들기로 결정했으며 구조는 다음과 같습니다.

중요한 파일을 강조하기 위해 녹색 화살표를 사용했습니다. 그리고 우리는 그 파일이 무엇을 포함하고, 무엇이 목적인지를 살펴볼 필요가 있습니다. 거의 모든 Scala 프로젝트는 build.sbt 파일에서 시작합니다. 원칙적으로 프로젝트에 대한 메타 정보가 포함되어 있습니다 : 이름, 버전, 의존성 ...

1
2
3
4
5
6
7
8
9
name := "sbt-build-example"
 
version := "1.0"
 
scalaVersion := "2.12.2"
 
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-http" % "10.0.9"
)

 

위의 코드에서 종속성을 살펴보십시오. 보다 정확하게 말하면 단일 종속성을 살펴보십시오. 그런 다음 모든 "비즈니스 로직"이 시작되는 Main 클래스를 살펴볼까요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example
 
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
import scala.concurrent.Future
 
object Main extends App {
 
  implicit val system = ActorSystem()
  implicit val ec = system.dispatcher
  implicit val materializer = ActorMaterializer()
 
  val routes = pathPrefix("ping") {
    get {
      complete("pong")
    }
  }
 
  val bindingFuture: Future[ServerBinding] = Http().bindAndHandle(routes, "127.0.0.1", 5000)
 
}

 

데모 응용 프로그램은 127.0.0.1/ping으로 GET 요청을 보내면 "pong"을 반환하는 엔드포인트 하나만 있는 간단한 프로그램입니다..그럼 이 앱을 빌드하는 방법 및 디플로이 가능하게 만드는 방법은 무엇일까요?
이 작업을 수행하려면 sbt-native-packager 플러그인을 프로젝트에 추가 해야하며 매우 간단합니다.
plugins.sbt 파일에 한 줄의 코드를 추가하면됩니다.

 

1
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.0")

 

그리고 build.sbt 파일에 다음과 같이 추가합니다.

 

1
enablePlugins(JavaAppPackaging)

 

그리고 콘솔을 열고 프로젝트의 루트 폴더로 이동하여 다음 명령을 실행합니다.

 

1
sbt stage

 

이 명령은 프로젝트 디렉토리에 새 폴더를 생성합니다.

target/universal/stage/

 

stage 디렉토리에는 bin & lib라는 두 개의 폴더가 있을 것 입니다.첫 번째는 각 운영체제에서의 런처(Linux / Mac / Windows)를 포함합니다. 두 번째 것은 모든 종속성과 응용 프로그램 클래스가있는 jar를 포함합니다.그 다음은 무엇을 해야 할까요? 추측 하듯이, 단순합니다. 그냥 스테이지 디렉토리를 모두 서버로 옮기고 콘솔에서 다음 명령을 실행하여 시작하면 됩니다.

 

1
./bin/app-name
 

당연히 이 명령은 stage 폴더에서 실행해야하며 app-name은 build.sbt 파일에서 지정한 프로젝트 이름 (패키지 이름)에 따라 달라질 수 있다는것 쯤은 눈치채셨겠죠? 
이상 모든게 끝났습니다!!! 매우 쉽죠? ^^

 

요약 

 

보시다시피 sbt-native-packager는 Scala 프로젝트를 빌드하는 꽤 직접적인 방법을 제공합니다. 더 멋진 점은 Scala 앱을 위한 패키징 옵션이 훨씬 많다는 것입니다. 이 기사에서는 이 플러그인이 수행 할 수있는 기능의 5 % 만 설명했습니다. 따라서 앞으로 sbt-native-package에 대한 몇 가지 새로운 자습서를 작성하려고 합니다.


스칼라 리플렉션 

 참고: http://3x14159265.tumblr.com/post/57543163324/playing-with-scala-reflection

리플렉션에 대해서 생소하다면 먼저 자바 리플렉션 관련 읽을 거리를 읽어보세요.

1. 자바 클래스로더 이야기
2. 자바 리플렉션 사용하기 


스칼라 2.10에서 새로운 리플렉션 API 가 소개되었습니다. 

리플렉션으로 보통 우리는 

  1. 제네릭 타입을 포함하여 객체의 타입을 조사한다.
  2. 런타임에 클래스(파일 혹은 네트워크를 통해서 전해온 바이트를 통해) 객체를 생성한다.
  3. 그 객체의 메소드를 호출한다.


를 할 수 있게 됩니다.

하나씩 살펴 볼까요?  먼저 아래와 같이 임포트 해줍니다.

import scala.reflect.runtime.universe._


객체와 클래스들은 런타임에 클래스로더와 runtime mirror 를 이용하여 인스턴스화 될 수 있습니다.
객체와 클래르 생성이 약간 다릅니다.

def getObjectInstance(clsName: String): ModuleMirror = {
val mirror = runtimeMirror(getClass.getClassLoader)
val module = mirror.staticModule(clsName)
mirror.reflectModule(module).instance
}
def getClassInstance(clsName: String): Any = {
val mirror = runtimeMirror(getClass.getClassLoader)
val cls = mirror.classSymbol(Class.forName(clsName))
val module = cls.companionSymbol.asModule
mirror.reflectModule(module).instance
}

다음과 같은 object 가 있다고 가정 하면  (파일로 있어도 되고, 바이트 배열로 있어도 된다)

package models.entities
object User {
def findByUsername(username: String) = {
// database or whatever magic is done here ;)
}
}

런타임에 이 object 의 메소드를 호출 할 수 있습니다.  (즉 원격에서 클래스를 가져와서 호출해 줄 수도 있다는 의미. 예를들어 분산 컴퓨팅시 동일한 작업을 여러 컴퓨터에서 분산 시켜서 하고자 할 때도 사용 될 수 있겠지요.)

def invokeMethod(param: String) = {
val im =mirror.reflect(getObjectInstance("models.entities.User"))
val method = im.symbol.typeSignature.member(newTermName("findByUsername")).asMethod
im.reflectMethod(method)(param)
}

물론 스칼라의 새 리플렉션 API 는 훨씬 더 많은 것을 포함하고 있습니다.  다음 문서를 참고하세요. scala docs.


+ Recent posts