* 해당 글은 모던 자바 인 액션 Chapter 20을 요약한 글입니다.
해당 챕터에서는 함수형 프로그래밍을 더욱 자극하기 위하여 자바와 스칼라를 비교하여 함수형 프로그래밍에 대한 호기심을 만족시켜주기 위하여 작성되었다. 하지만 나는 스칼라에 대해 정리하려고 한다.
Scala란?
- 함수형 프로그래밍 언어로 자바의 복잡함을 단순화 시키기 위하여 만들어진 언어이다.
- 자바와 같이 JVM 위에서 돌아간다.
- Scala의 자료구조는 리스트, 집합, 맵, 스트림, 튜플, 옵션 등을 제공한다.
- Scala는 Java interface 대신에 트레이트를 사용한다.
스칼라 구조
Class Hello {
val Hello = "Thanks for reading our book")
def sayThankyou() {
printlin(Hello)
}
}
val는 필드를 말하며, def는 함수를 말한다.
스칼라의 Constructor, Getter, Setter
class Student(var name: String, var id: Int)
val s = new Student("Raoul", 1)
println(s.name) // 이름을 얻어 Raoul 출력
s.id = 1337 // id를 1337로 변경
println(s.id)
스칼라는 생성자, 게터, 세터가 암시적으로 생성되므로 자바 같이 만들어줄 필요는 없다.
추가로 생성자에 대해 커스텀마이징을 하여 특정 필드만 받을 수 있도록 만들 수도 있다.
명령형 스칼라
object Beer {
def main(args: Array[String] {
var n : Int = 2
while( n <= 6 ) {
println(s"Hello ${n} bottles of beer")
n +=1
}
}
}
함수형 스칼라
object Beer {
def main (args: Array[String]) {
2 to 6 foreach { n => println(s"Hello ${n} bottles of beer} }
}
}
명령형 스칼라와 함수형 스칼라를 비교하였을 때 확실히 함수형 스칼라가 더 직관적이고 우아하게 보인다.
이렇듯 스칼라는 함수형 프로그래밍에 특화되어 있다.
개인적인 생각이지만, 코틀린은 스칼라와 자바의 사이에서 태어난 아들 같아 보인다.😂
스칼라의 기본 자료구조 : 리스트, 집합, 맵, 튜플, 스트림, 옵션
val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)
val authors = List("Raoul", "Mario", "Alan")
val numbers = Set(1,1,2,3,5,8)
이렇게 Map / List / Set 도 간단하게 만들 수 있다. 여기서 val를 계속 쓰이는데 이러한 val는 객체에 대해 인스턴스화하면 그에 맞게 변경이 되는 것을 타입 추론이라고 하는데, 스칼라의 장점 중 하나이다.
자바는 Java10부터 var 라는 키워드를 사용하여 타입 추론이 가능하다.
불변과 가변
현재까지 만든 Map / List / Set은 모두 불변이라고 할 수 있다. 즉, 스칼라는 컬렉션에 대해서 불변을 지켜주며 암묵적인 데이터 의존성을 줄이게 한다.
val authorsToAge = mutable.Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)
val authors = mutable.List("Raoul", "Mario", "Alan")
val numbers = mutable.Set(1,1,2,3,5,8)
가변적으로 만들려면 위의 예시처럼 컬렉션 앞에 mutable을 붙이면 된다.
컬렉션 사용하기
val linesLongUpper = fileLines filter (_.length() > 10 ) map(_.toUpperCase())
// _.length()는 l => l.length()로 해석할 수있다.
// filter, map으로 각각 파이프라인 처리가 가능하다.
val linesLongUpper = fileLines.par filter (_.length() > 10) map(_.toupperCase())
// parellel
스칼라의 컬렉션 동작은 스트림 API와 비슷하고, 예시처럼 par라는 메서드를 통하여 파이프라인 병렬처리도 가능하다.
튜플
val raoul = ("Raoul", "+ 44 887007007") // 2개 튜플
val book (2018, "Modern Java in Action", "Manning") // 3개 튜플
var numbers = (42, 1338, 0, 3, 14) // 5개 튜플
println(book._1) // 2018 출력
println(numbers._4) // 3 출력
스칼라는 자바에는 없는 두개 이상의 요소를 그룹화하는 튜플이라는 컬렉션을 가지고 있다.
스트림
스칼라에서는 게으르게 평가되는 자료구조인 스트림을 제공한다.
스트림을 통하여 이전 요소가 접근할 수 있도록 기존 계산값을 기억하고, 인덱스를 제공하므로 리스트처럼 인덱스로 스트림의 요소에 접근 할 수 있다. -> 이러한 기능 추가로 값에 대해서 캐싱해야하기 때문에 자바의 스트림에 비해 메모리 효율성이 조금 떨어진다.
옵션
자바의 Optional 대신 Option을 제공한다. 즉, NPE를 방지시킬 수 있다.
함수
스칼라의 함수는 어떤 작업을 수행하는 일련의 명령어 그룹이다.
스칼라의 함수는 아래와 같은 기능을 제공한다.
- 함수 형식 : 함수형 인터페이스에 선언된 추상 메서드의 시그니처를 표현하는 개념.
- 익명 함수 : 익명 함수는 자바의 람다 표현식과 달리 비지역 변수 기록에 제한을 받지 않는다.
- 커링 지원 : 커링은 여러 인수를 받는 함수를 일부 인수를 받는 여러 함수로 분리하는 기법이다.
스칼라의 일급 함수
스칼라의 함수는 일급값(first-class value)이다. 즉, Integer나 String처럼 함수를 인수로 전달하거나, 결과로 반환하거나, 변수에 저장할 수 있다.
익명 함수와 클로저
스칼라도 익명 함수의 개념을 지원한다.
익명 함수란? https://en.wikipedia.org/wiki/Anonymous_function
클로저
클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다. 하지만 자바의 람다 표현식에는 고칠 수 없는 제약이 있다. 하지만 스칼라는 자바와 다르게 변수를 캡처할 수 있어서 클로저 기능을 사용할 수 있다.
커링
// 1)
def multiplyCurry(x :Int)(y :Int) = x * y // 커링 함수
var r = multiplyCurry(2)(10) // 2함수를 실행시키고 10을 시켜 2x10을 한다.
// 2)
val multiplyByTwo :Int => Int = multiplyCurry(2)
val r = multiplyByTwo(10) // 결과 값 20
스칼라에서도 커링이라는 기법을 제공한다. 커링이란 x와 y라는 두 인수를 받는 함수 f를 한 개의 인수를 받는 g라는 함수로 대체하는 기법이다.
해당 스칼라에 대한 설명의 끝은 바로 트레이트에 대해서 설명하고 끝내려고 합니다.
트레이트
트레이트는 다중 상속을 지원하는 자바의 인터페이스와 디폴트 메서드 기능을 합친 것으로 클래스와 조합해서 선언할 수 있다.
trait Size {
var size : Int = 0
def isEmpty() = size ==0
}
트레이트는 위와 같이 Trait로 선언을 하면 된다.
// 클래스 상속
class Empty extend Sized
println(new Empty().isEmpty())
// 인스턴스화 과정에서 조합
var b1 = new Box() with Sized
println(b1.isEmpty())
트레이트는 클래스 상속도 가능하지만 신기하게도 인스턴스화 과정에서도 상속을 통하여 조합을 할 수가 있다.
회고
스칼라에 대해서 잠깐 시간내어 공부해 봤는데, 스칼라와 코틀린과 같은 함수형 프로그래밍 언어는 간결하고 우아하다라는 것을 항상 느끼는 것 같다. 그리고 현재 다니고 있는 회사에서 자바만 사용하기 때문에 자바를 사용하고 있는데 다시 코틀린을 사용하고 싶어졌다.😭