KotlinLanguage

Kotlin 무작정 따라해보기 2일차 - Introduction > Hello World, Functions

2일차 시작하기 - Learn Kotlin by Example

지난 1일 차 마무리에서 이야기 한 것처럼 2일 차부터는 Learn Kotlin by Example을 무작정 따라 하면서 코틀린(Kotlin)을 배워보려 한다.

첫날 설치했던 IntelliJ IDEA를 이용해서 예제를 따라 해도 되고, 아니면 해당 사이트에 있는 Playground를 이용해서 예제를 실습해봐도 된다.
나는 실습해야 하는 예제가 있으면 PlaygroundIDEA를 번갈아 가면서 사용해볼 예정이다.
본격적인 시작에 앞서 앞으로 배워야 할 것에 대한 목차만 한번 살펴보자.

  • Introduction
  • Control Flow
  • Special Classes
  • Functional
  • Collections
  • Scope Functions
  • Delegation
  • Productivity Boosters
  • Kotlin/JS

총 9개의 세션으로 나눠어 있는 것 같다. 그리고 이것을 앞에서 말했던 것처럼 Playground 사이트나 PC에 설치한 IDEA로 실습(?)해 볼 수 있는 것 같고, 나아가, 좀 더 코틀린 문법에 익숙해지라고 Kotlin Koans라는 것도 제공하고 있다.
이건 Koans가 무슨 뜻인지 모르겠지만, 설명을 보니 대충 실습하라고 만들어 둔 것 같다.
앞으로 9개의 세션을 하루에 1개 세션씩 끝내서 9일 동안 진행하고, 나누어서 진행하고, Kotlin Koans의 실습 부분을 해볼까 한다.
그리고, Learn Kotlin by Example의 내용만으로 정확하지 않거나 설명이 부족 할 수 있으니 https://kotlinlang.org/docs/reference/를 참고해서 예제에서 설명하고자 하는 것을 찾아 내용을 보충해 보겠다.

Introduction

Hello World

자. 다시 나왔다. 모든 언어의 시작인 Hello World Kotlin에서 Hello World를 출력하는 코드를 가지고 Kotlin의 시작과 기본 문법(Basic Syntax)를 알려 주려고 하는 것같다.
Kotlin ReferenceGetting Started - Basic Syntax를 참고해서 같이 보면 좋을 것 같다.

package org.kotlinlang.play         // 1

fun main() {                        // 2
    println("Hello, World!")        // 3
}
  1. 첫 줄의 package org.kotlinlang.play는 내가 작성하고자 하는 프로그램의 Package 지정 한다.

    • Package 이름은 정의하지 않아도 상관 없다. 만약 Package를 정의하지 않으면 기본 Package로 지정 된다.
    • Package 이름을 정의 할 때는 소스파일의 가장 상위에 위치 해야 한다.
    • 소스파일의 디렉토리와 Package가 일치할 필요는 없다.
  2. 모든 프로그램의 시작점(entry point)라 할 수 있는 main()함수 이다.

    • Kotlin 1.3버전부터 main() 선언시 파라미터를 지정하지 않아도 된다. 이경우 함수가 아무것도 반환하지 않는 다는 의미 이다.

    Kotlin 1.3 이전 버전에서는 main(args: Array<String>)라고 지정해야 한다. 그래서 Playground로 실험 해봤다.
    Playground에서 우측에 있는 톱니바퀴 모양의 아이콘을 누르면 Kotlin 버전을 변경 할 수 있다.

  3. println()은 콘솔(console)에 출력하는 예약 함수(?)라고 보면 될 것 같다.

    • 코드를 입력하고 마지막에 세미콜론(;)은 입력하지 않아도 된다. (선택사항)

Functions

이제 Function(함수)의 사용 방법에 대해 알아 보자.
Reference - Functions And Lambdas - Functions를 같이 보면 좋을 것 같다.

Default Parameter Values and Named Arguments

fun printMessage(message: String): Unit {                               // 1
    println(message)
}

fun printMessageWithPrefix(message: String, prefix: String = "Info") {  // 2
    println("[$prefix] $message")
}

fun sum(x: Int, y: Int): Int {                                          // 3
    return x + y
}

fun multiply(x: Int, y: Int) = x * y                                    // 4

fun main() {
    printMessage("Hello")                                               // 5
    printMessageWithPrefix("Hello", "Log")                              // 6
    printMessageWithPrefix("Hello")                                     // 7
    printMessageWithPrefix(prefix = "Log", message = "Hello")           // 8
    println(sum(1, 2))                                                  // 9
    println(multiply(2, 4))                                             // 10
}
  1. printMessage() 함수는 String타입의 message라는 변수명을 파라미터로 받아 리턴 값 없이 message의 내용을 콘솔에 출력하는 함수 이다.

    • ParameterName: ParameterType 형태로 파라미터를 정의 한다.
    • 타입을 생략 할 수 없다.
    • printMessage()) 뒤에 붙은 UnitprintMessage()함수의 리턴 타입을 지정하는 것이다.

      UnitJava에서 void 같다 한다. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/

  2. printMessageWithPrefix() 함수는 String타입의 message 변수와, String타입의 prefix 변수를 파라미터로 받고 리턴 값 없이 messageprefix를 콘솔에 출력하는 함수다. 단, prefix 값이 없을 경우 Info를 기본값으로 지정한다.

    • ParameterName: ParameterType = "DefaultValue" 형태로 함수 호출시 파라미터 값을 지정하지 않으면 기본값을 사용하게 된다.
    • 1번의 parintMessage()함수와 동일하게 리턴 값이 없지만 리턴 타입을 지정하지 않았다. 즉, 리턴 타입이 없을 경우 Unit을 생략 할 수 있다.
    • 파라미터를 2개 이상 지정할 때는 콤마(,)로 구분 한다.
  3. sum() 함수는 Int 타입의 x와, Int타입의 y를 입력 받아, xy를 더한 후 리턴 한다.

    • 함수의 리턴 값이 존재 할 때는 리턴값의 리턴 타입을 지정해야 한다.
    • return 키워드를 사용하여 함수의 리턴 값을 전달 한다.
    • 코틀린에서 더하기를 할때는 + 키워드를 사용 한다.
  4. multiply() 함수는 Int 타입의 x와, Int타입의 y를 입력 받아, xy의 곱한뒤 리턴 한다.

    • 중괄호({})와 return 키워드를 사용하지 않고 = 대입(할당) 연산자를 사용하였다.
    • 함수의 리턴 타입을 지정하지 않았다.
    • 함수의 내용으로 타입을 추촌하여 반환 하였기 때문에 반환 타입을 작성하지 않은 것 같다.

    궁금! sum() 함수도 반환 타입을 지정하지 않아도 될까?
    결론. +이기 때문에 당연 Int가 반환 될 것이라 생각했지만, Type mismatch: inferred type is Int but Unit was expected 에러가 발생 했다.

  5. printMessage() 함수를 호출하며 인자값으로(argument) Hello를 입력하여 호출 하였다.

    • 결과로 Hello가 출력 될 것이다.
  6. printMessageWithPrefix() 함수를 호출하며 인자값으로 HelloLog을 입력하여 호출 하였다.

    • 결과로 [Log] Hello가 출력 될 것이다.
  7. printMessageWithPrefix() 함수를 호출하며 두번재 인자(argument)를 생략하고 Hello만 입력하여 호출 하였다.

    • 결과로 [Info] Hello가 출력 될 것이다.
    • 2번재 파라미터가 없기 때문에 prefix의 기본값인 Info가 사용 되었다.
  8. printMessageWithPrefix() 함수를 호출하며 인자값으로 prefix = "Log", message = "Hello"를 입력하여 호출 하였다.

    • 일반적으로 Function(함수)호출시 정의된 인자(argument) 순서대로 데이터를 입력해야 하지만, Kotlin에서는 인자명(argument name)과 값(value)을 함께 전달 하면 정의된 인자의 순서와 상관 없이 사용 가능하다.
    • 결과로 [Log] Hello가 출력 될 것이다.
  9. sum()함수의 결과를 출력한다.
  10. multiply()함수의 결과를 출력한다.

Infix Functions

난생 처음 듣는 이름 이다. 한국말로 어떻게 불러야 할지도 모르겠다. 구글로 번역하면 중위 함수(Infix Functions)라고 나오는데 이게 맞는지도 모르겠다.
단일 파라미터를 가지고 있는 멤버 함수나 확장 함수일 경우에는 Infix Function으로 표현 할 수 있다고 하는데, 자세한건 Reference - Functions and Lambdas - Functions 참고 하자.
일단 무조건 따라하기니 한번 따라해보자.

fun main() {

  infix fun Int.times(str: String) = str.repeat(this)        // 1
  println(2 times "Bye ")                                    // 2

  val pair = "Ferrari" to "Katrina"                          // 3
  println(pair)

  infix fun String.onto(other: String) = Pair(this, other)   // 4
  val myPair = "McLaren" onto "Lucas"
  println(myPair)

  val sophia = Person("Sophia")
  val claudia = Person("Claudia")
  sophia likes claudia                                       // 5
}

class Person(val name: String) {
  val likedPeople = mutableListOf<Person>()
  infix fun likes(other: Person) { likedPeople.add(other) }  // 6
}
  1. Int에 Infix extension function을 정의 한다.

    • Int가 단순 키워드인줄 알았는데 이것도 하나의 객체(Object)인거 같다.
    • times() 함수를 Int의 확장 함수로 정의하고 String타입의 str 파라미터를 지정 한다.
    • times() 함수는 전달 받은 인자(str)의 String 타입의 repeat()를 호출 한다. repeat() 호출시 전달하는 인자는 Int 자신(this)이다(?)
  2. 1번 infix function 에 대한 출력 값으로 Bye Bye 가 출력 된다.

    • Int 객체에 times() 확장 함수를 추가 하고 이를 2 times "Bye "라는 형식으로 호출 하였다.
    • 2 times "Bye "에서 2Int 타입(객체)이기 때문에 Int에 방금 추가한 times()를 함수를 사용하고 인자로 "Bye "를 넣어 준 것이다.
    • 이것을 다르게 표현하면 2.times("Bye ")와 같지 않을까 생각 한다. (확실하지 않음)
    • 1번 times() 함수안에서 전달 받은 인자 str 파라미터가 String 타입(객체)이고 해당 String의 라이브러리인 repeat()를 호출, 인자로는 Int.times() 자신인 this를 넘겨 주었으니 2이가 들어가서, "Bye "라는 문자가 2회 출력 된것으로 보인다.
    • 여기서 유추 할 수 있는 것은 확장 함수(extension function)의 this는 부모 객체라는 것이다.
  3. val 키워드로 pair라는 변수를 정의하고, 값으로 "Ferrari" to "Katrina"의 값을 담는다.

    참고로 val은 read-only 프로퍼티(property) 나 지역 변수(local variable)를 선언하는 것이다.

    • 여기서 to라는 infix function이 사용 되었는데 이는 기본 라이브러리로 제공되는 infix function 이다.
    • 기본 라이브러리에서 제공하는 infix function이 존재하는 것 같다.
  4. Pair 라는 기본 라이브러리 함수를 이용해서 String 객체에 onto() 라는 infix function 을 추가 한다.

    • 처음에는 3번에는 정의한 val pair = "Ferrari" to "Katrina"를 이용해서 Pair()이란 함수를 만든줄 알았으나, 이름을 변경하니 에러가 나는 것을 발견하고 찾아보니 Pair() 함수는 코틀린이 제공하는 기본 라이브러리 였다.
    • 3번에서 정의한 pair와 코틀린에서 기본 제공하는 Pair()가 하는 동작은 동일 했다.
    • 4번의 경우 Pair()를 이용해서 String객체에 새로운 infix function을 정의하여 사용하는 방식을 보여주려 한것 같다. (이런식으로도 쓸수 있어를 보여주려 한듯.)
  5. Person이란 Classlinkes라는 Members Function(Method)Infix로 표현하고 이를 활용하여 별도로 생성한 클래스를 이용하는 방식을 보여주는 것 같다.

    • sophia라는 Person객체에 claudia이라는 Person객체를 추가 한다.
    • Todo: 좀더 따라해서 나중에 Person 객체안의 likedPeople 변수의 내용을 확인해 보자.

지금 사용된 예제의 경우 main() 함수 안에서 function을 선언하는 local functions을 사용했다.
코틀린은 Function내에서 Local Function을 지원한다는 것을 보여주려는 것 같다. 그럼 당연히 Closure도 지원이 되겠지?

Operator Functions

연사자 함수(?), 코틀린은 어떤 함수들은 연산자로 업그레이드가 가능하다. Operator overloading이라는 건데, operator 키워드로 함수를 정의하면 된다.
infix function의 사용과 방식이 비슷한 것 같다.

operator fun Int.times(str: String) = str.repeat(this)       // 1
println(2 * "Bye ")                                          // 2

operator fun String.get(range: IntRange) = substring(range)  // 3
val str = "Always forgive your enemies; nothing annoys them so much."
println(str[0..14])                                          // 4
  1. Int* 연산자를 overloading 해서 str.repeat(this)로 변경 한다.

    • times()가 그냥 지은 이름인 줄 알았는데, 알고 보니 Binary operations 중 하나였다.
    • 이런식으로 연산자별로 오버로딩하기 위한 특정 Member Function이 있고, 해당 Member Function을 재정의 해야 한다.
  2. * 연산자의 변경된 내용을 이용하여 2 * "Bye "를 출력 한다.

    • 위에 infix function으로 했을땐 2 times "Bye "로 표현 했지만, Operator를 오버로딩하여 2 * "Bye "형식으로 표현 하였다. 표현만 보자면 후자 방식이 더 좋은것 같다. 하지만, 이렇게 쓰면 모든 곱셈이 이렇게 변경될 것이니 조심 해야 할듯;
    • 출력 결과는 Bye Bye다.
  3. Indexed access operatorget()Overloading 한다.

    • a[i] 형식의 표현을 변경한다고 한다. 코틀린에서는 String의 글자 하나마다 Index를 가지고 있나 보다.
    • IntRange라는 타입은 정수로 범위를 지정하는 타입 같다. 0..14 이런식으로 ..으로 연결해 사용하는 듯 하다.
    • substring() 코틀린에서 제공하는 기본 라이브러리다.
  4. str[] 형식의 연산자의 변경된 내용이 출력 된다.

    • 출력 결과는 Always forgive다.
    • IntRange에 대해 잘 모르겠지만, 0부터 시작하여 14까지 간거 같다. 총 15자리를 가져온 것.

Functions with vararg Parameters

함수에 가변인자를 사용할 수 있다. 함수의 파라미터를 정의 할때 vararg 키워드를 파라미터 앞에 붙여 주면된다.
함수의 인자를 넘길때 쉼표(,)를 이용해서 인자를 넘기면 함수에 정의한 파라미터에 인자가 배열 형태로 담겨진다.

fun printAll(vararg messages: String) {                            // 1
    for (m in messages) println(m)
}
printAll("Hello", "Hallo", "Salut", "Hola", "你好")                 // 2

fun printAllWithPrefix(vararg messages: String, prefix: String) {  // 3
    for (m in messages) println(prefix + m)
}
printAllWithPrefix(
    "Hello", "Hallo", "Salut", "Hola", "你好",
    prefix = "Greeting: "                                          // 4
)

fun log(vararg entries: String) {
    printAll(*entries)                                             // 5
}
  1. printAll()함수에 String 타입의 messages 파라미터를 추가하고 vararg로 선언(가변 인자)

    • printAll() 함수의 내용은 messages에 담겨진 내용을 단순 println() 로 출력 하는 것.
    • 처음으로 for 의 사용방법이 나옴.
  2. 콤마(,)로 구분하여 printAll()함수를 호출

    • 출력 결과

       Hello
       Hallo
       Salut
       Hola
       你好
      
  3. printAllWithPrefix()함수의 파리미터를 printAll()과 동일하게 설정하고, 추가로 String타입의 prefix 파라미터를 추가로 선언
  4. vararg 선언된 messages에 담길 내용들은 콤마(,)로 구분해서 입력하고, prefix의 경우 명시적으로 파라미터 이름까지 지정하여 인자를 추가하여 printAllWithPrefix() 함수 호출

    • 출력 결과

       Greeting: Hello
       Greeting: Hallo
       Greeting: Salut
       Greeting: Hola
       Greeting: 你好
      
  5. vararg를 사용하는 printAll() 함수를 이용하는 추가적인 방법으로 log()함수를 선언하고 String타입의 entries 파라미터를 vararg로 선언

    • printAll()함수를 호출하면서 entries파라미터 값을 전달 할 때 앞에 * 기호가 나왔는데 이는 vararg 파라미티ㅓ를 그대로 전달 하려는 특수 스프레드 연산자(special spread operator) 기호이다.
    • Runtime시에는 vararg는 단순 Array<String>의 배열일 뿐이라서 Stringvararg를 그대로 전달하기 위해 * 연산자를 사용함

2일차 마무리 및 Next

처음 시작할때는 Introduction의 모든 내용은 하루 정도면 충분할줄 알았는데, 생각보다 내용이 많고 검색 할 것이 많았던 것 같다.
다음 시작에는 Introduction을 이어서 Variables를 진행 해보겠다. (왠지 하루에 하나씩 끝내기도 힘들듯 하다.)