[코틀린으로 배우는 함수형 프로그래밍] 2장 1- 6에 해당하는 코틀린 문법에 대한 내용입니다.
안녕하세요, 휴먼스케이프 프론트엔드 개발자 Tasha입니다. 휴먼 Tech팀의 세번째 공통 교양 책인 [코틀린으로 배우는 함수형 프로그래밍]에서 본격적으로 함수형 프로그래밍을 시작하기전에, 먼저 코틀린 언어의 문법에 대해서 간단히 짚고 넘어가는 2장 부분에 대해 이야기 해보려고 합니다.
출처: https://blog.insightbook.co.kr/2019/12/12/%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/
먼저 이 책에서 함수형 프로그래밍을 배우기 위해 코틀린을 선택한 이유는 다음과 같습니다.
1) 코틀린은 자바와 형태가 유사해 스칼라에 비해 상대적으로 공부하기도 쉽고 문법도 간단하다. 따라서 함수형 프로그래밍 자체에 집중하기가 수월하다.
2) 실전 프로젝트를 개발할 때 순수 함수형 언어를 쓰는 경우가 드물다. 코틀린은 하이브리드형 언어로, 순수 함수형이 아닌 언어로 함수형 프로그래밍을 할 때 시행착오와 대처법도 다루기 때문에 도움이 많이 된다.
그럼 지금부터, 책의 2장인 [코틀린으로 함수형 프로그래밍 시작하기]의 소제목 1–6 부분을 살펴보겠습니다.
2–1. 프로퍼티 선언과 안전한 널 처리
프로퍼티 선언
var value: Int = 10 // var - 가변 프로퍼티 선언
val city: String = “London” // val - 읽기 전용 프로퍼티 선언
var variable = 10 // 타입 생략한 선언
위와 같이 타입과 함께 가변(var: variable) / 읽기 전용(val: valuable) 프로퍼티를 선언할 수 있고, 타입선언을 생략할 수도 있습니다.
안전한 null 처리
Java에서는 NPE(NullPointerException)에 대처하기 위해 참조타입을 다루기 위해서는 항상 null값을 대비하여 프로그래밍을 해야 합니다. 코틀린은 자바와 다르게 Nullable, Non-nullable 타입을 따로 지정하여, Non-nullable 타입의 객체는 null check가 필요없도록 할 수 있습니다.
var a: String = 'abc' // Non-nullable 타입 선언 a = null // compilation error
var b: String? = 'abc' // Nullable 타입 선언 b = null // ok
var l = b.length // error: variable 'b' can be null
Nullable한 객체의 프로퍼티 접근을 하면 위와 같이 컴파일러가 에러를 내므로, 이를 위해서는 다음과 같은 안전한 접근이 필요합니다.
1) null 조건 체크 val l = if (b != null) b.length else -1
2) safe call operator 이용 b?.length -> b가 null이면 null을, 그렇지 않으면 b.length을 리턴
2–2. 함수와 람다
함수
코틀린에서 함수는 fun이라는 예약어를 통해 선언합니다.
코드블록({})과 return과 함께 사용한 함수
fun multiply(x: Int, y:Int = 2): Int { return x * y * 2 }
{}로 감싸져 있는 부분은 타입 추론을 하지 않기 때문에 함수의 반환 타입을 항상 명시해야합니다. 반환타입을 명시하지 않으면 Unit타입을 반환하는데, 이는 자바의 void와 같이 아무것도 반환하지 않음을 명시합니다. Unit타입은 일반적으로 타입선언을 생략합니다.
변수의 y: Int = 2부분처럼, 기본값 설정도 가능합니다.
println(multiply(2)) // '8'출력 println(multiply(y = 10, x = 1)) // '20'출력
매개변수의 순서에 따라 값이 할당되며, 매개변수의 이름과 함께 부르면(named arguments) 순서와 상관없이 부를 수도 있습니다.
2. =와 함께 선언되는 함수 (Single-expression functions)
fun twice2(value: Int): Int = value * 2 fun twice3(value: Int) = value * 2
본문이 하나의 표현식으로 구성되는 경우에는 코드블록과 return을 생략할 수 있습니다. =연산자를 사용해서 함수의 이름에 함수의 본문을 할당하고, 이 경우에는 타입 추론이 가능하기 때문에 반환타입 선언을 생략할 수 있습니다.
람다표현식과 익명함수(function literals)
람다 표현식과 익명함수는 선언되지는 않았지만 표현식과 같이 전달되는 형태입니다. 코틀린을 비롯한 함수형 프로그래밍 언어들은 람다식을 이용해 익명함수를 간결하게 표현합니다.
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val sum = { x: Int, y: Int -> x + y } // 위의 함수를 간결하게 표현한 것
람다표현식은 대괄호로 둘러싸이며, 매개변수 선언이 포함되고, ->(화살표) 뒤에 body가 정의되는 형태의 표현식입니다. {} 안에 있는 타입 정의는 선택사항으로, lambda의 return 타입이 Unit이 아니라면, 마지막 표현식이 return 값이 됩니다.
2–3 제어구문 (if, when, for)
if문
코틀린에서 if문은 기본적으로 어떤 결과값을 반환할 때 쓰는 표현식으로, 결과값이 없을 때는 구문으로 사용됩니다.
val max = if (a > b) { print('Choose a') a // 마지막 표현식이 반환 값 } else { print('Choose b') b // 마지막 표현식이 반환 값 }
위와 같이 if문을 표현식으로 사용할 때는 반드시 else가 필요하며, 그렇지 않을 경우에는 컴파일 오류가 발생합니다.
when문
when 역시 if문과 같이 표현식과 구문 두가지 형태로 사용될 수 있습니다. 또한 if문과 같이 하나의 branch가 block처럼 사용될 수 있으며, 블락의 마지막 표현식이 when문의 값이 됩니다.
when (x) { 1 -> print('x == 1') 2 -> print('x == 2') else -> { // Note the block print('x is neither 1 nor 2') } }
else 브랜치는 조건이 어떠한 브랜치와도 매칭되지 않았을 때 실행됩니다. 컴파일러가 브랜치 조건들이 모든 가능한 케이스를 다 다룬 것이라는 부분을 증명할 수 없다면, else 브랜치는 필수입니다.
when { x.isOdd() -> print('x is odd') x.isEven() -> print('x is even') else -> print('x is funny') }
이렇게 분기조건을 조건문으로 작성하는 경우에는 when(x)에서 (x)를 생략할 수 있습니다.
for문
// 기본 for (item: Int in ints) { // ... }
// index와 함께 for ((index, value) in array.withIndex()) { println('the element at $index is $value') }
// range 설정 for (i in 1..3) { println(i) // '123' } for (i in 6 downTo 0 step 2) { println(i) // '6420' }
리스트와 같은 컬렉션에서 for (item in collection) 이런식으로 아이템을 하나씩 꺼내서 처리할 수 있으며, 아이템과 인덱스를 한꺼번에 꺼내고 싶으면 withIndex함수를 사용하면 됩니다.
2–4. 인터페이스
인터페이스
코틀린에서 제공하는 인터페이스는 다음과 같은 특징을 갖고 있습니다.
메소드 구현과 추상화 메소드를 가질 수 있다.
interface MyInterface { fun bar() fun foo() { println('Foo') } }
state를 저장할 수 없고, property를 가질 수는 있으나 추상화되거나 접근자 구현이 필요합니다.
interface User { val fullName:String // 오버라이드 필요 val firstName: String // 오버라이드 불필요 get() = fullName.substringBefore(' ') //error val firstName :String= fullName.substringBefore(' ') } class UserChild(override val fullName: String) : User
추상프로퍼티는 게터를 통해 초기화될 수 있으며, 초기화 되지 않은 경우에는 상속한 클래스에서 재정의를 해주어야 합니다.
다중상속이 가능하다.
interface Computer { fun read() fun write() = Log.d('1001110') } interface Person { fun read() fun write() = Log.d('ABCDEFG') } class AI : Computer, Person { ... }
여러개의 interface를 상속하기 위해 쉼표 구분자를 사용하며, 디폴트 함수 이름이 같은 인터페이스를 상속하여 클래스를 정의하면 컴파일 에러가 발생하므로 override를 구현해주어야 합니다.
class AI : Computer, Person { //같은 이름의 메소드 override override fun read() { super.read() super .read() } override fun write() { L.d('1000 is 8 in decimal number.') } }
이름이 같은 메소드들이 있는 interface를 상속하려면 override를 구현해주고, 상위 메소드는 super<>를 이용해 접근할 수 있습니다.
2–5. 클래스
클래스
코틀린에서는 new 연산자 없이 새로운 객체를 생성하며, 클래스와 클래스 프로퍼티 선언/ 생성자 호출 예는 다음과 같습니다.
Class User(var name: String, val age: Int = 18); // 기본값 할당 가능
val user = User('FP', 32) println(user.name) // 'FP' 출력 user.name = 'kotlin' println(user.name) // 'kotlin' 출력
프로퍼티는 자바의 멤버변수와 달리 게터와 세터를 내부에서 자동으로 생성하며, 따라서 게터와 세터 메서드 없이 프로퍼티에 접근하여 값을 얻어오거나 수정할 수 있습니다.
var로 선언된 프로퍼티 -> 게터, 세터 모두 사용
val로 선언된 프로퍼티 -> 게터만 사용
data 클래스
데이터를 보유하기 위한 클래스를 쓸 때 사용됩니다. 이는 hashCode, equals, toString 함수와 같은 함수들을 자동으로 생성해주며, copy함수도 제공합니다.
data class Person(val name: String, val age: String)
fun main(args: Array) { val person1 = Person('Dawoon', 28) val person2 = Person('Dawoon', 28)
println(client1 == client2) var Tasha = client1.copy(age = 27) }
위와 같이 동등연산자(==)를 이용하면 내부 equals를 호출합니다. 또한 copy메소드를 사용하면 원하는 파라미터를 오버라이딩해서 데이터 클래스의 새로운 인스턴스를 생성할 수 있게 됩니다.
enum 클래스
상수를 선언하기 위한 클래스로, 이를 이용하면 코드가 단순해지고 가독성이 좋아진다는 장점이 있습니다. enum 클래스는 프로퍼티와 함수가 모두 타입이 동일해야한다는 제약이 있습니다.
enum class Color(val r: Int, val g: Int, val b: Int){ RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0,0,255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b }
fun getMnemonic (color: Color) = when(color) { Color.RED -> 'Richard' Color.ORANGE -> 'Of' Color.YELLOW -> 'York' Color.GREEN -> 'Gave' Color.BLUE -> 'Battle' Color.INDIGO -> 'In' Color.VIOLET -> 'Vain' }
fun main(args: Array){ println(getMnemonic(Color.RED)) } 출처: https://hongku.tistory.com/351
sealed 클래스
enum클래스의 타입 제약 없이 타입을 확장할 수 있는 클래스입니다.
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN // 모든 케이스를 커버했기 때문에 else가 필요 없음. }
2–6. 패턴 매칭
패턴 매칭
패턴 매칭이란 값/ 조건/ 타입 등의 패턴에 따라서 매칭되는 동작을 수행하게 하는 것입니다. 코틀린에서는 when을 이용해 패턴매칭이 가능하며, 리스트와 같은 매개변수를 포함하는 타입은 패턴매칭이 어렵다는 특징이 있습니다.
먼저 when을 이용한 패턴매칭을 살펴보겠습니다.
fun checkCondition(value: Any) = when { value == 'kotlin' -> 'kotlin' value in 1..10 -> '1..10' value === User('Joe', 76) -> '=== User' // 객체의 참조값 비교 value == User('Joe', 76) -> '== User(Joe, 76)'// 객체의 값 비교 value is User -> 'is User' else -> 'SomeValue' }
이런식으로 패턴매칭이 가능하나, List
fun sum(numbers: List): Int = when { numbers.isEmpty() -> 0 else -> numbers.first() + sum(numbers.drop(1)) }
지금까지 이 책에서 다룬 코틀린 문법 부분의 반 정도 내용에 대해 알아보았습니다. 뒤이어 코틀린 나머지 문법에 대해서도 살펴보고, 이후 본격적으로 함수형 프로그래밍에 대하여 포스팅 될 예정이니 기대해주세요! 🙂
[출처]
책 [코틀린으로 배우는 함수형 프로그래밍]
kotlin — null을 안전하게 처리하는 방법
null-safety
Lambda Expressions and Anonymous Function
interface 예제
data class copy
enum class 예제
Get to know us better! Join our official channels below.
Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io