jam 블로그

러닝스칼라 4장 - 함수 본문

IT Book Study/러닝 스칼라

러닝스칼라 4장 - 함수

kid1412 2020. 9. 28. 23:13
728x90

순수 함수

  • 입력 매개변수만을 가지고 계산을 수행한 후 값을 반환한다.
  • 함수 외부의 어떤 데이터도 사용하거나 영향을 주지 않는다.
  • 동일 입력에 대해 항상 같은 값을 반환한다.

주어진 입력으로 계산하는 것 이외에 프로그램의 실행에 영향을 미치지 않아야 한다, 즉 부수 효과(side effect) 가 없다

함수 정의

def multiplier(x:Int, y:Int):Int = {x * y}
  • def로 시작하며 이름이 따라온다 - 그 뒤 괄호 안에는 매개변수 목록이 온다 (매개변수 타입은 추론하지 않으므로 지정해야 한다) - 그 뒤 : 뒤에 결과 타입을 지정한다 (결과 타입은 추론이 가능하므로 생략할 수 있다, 재귀 함수면 생략 불가능) - 함수 본문은 표현식 또는 표현식 블록으로 구성되어 있으며, 마지막 줄이 함수의 반환값이 된다

프로시저

반환값을 가지지 않는 함수. 문장(println()호출 등)으로 끝나는 함수. Unit 타입으로 추론된다.

부수 효과(메소드 밖에 있는 상태(var 변수) 변경, I/O 수행 등)를 위해 실행

def log(d: Double) = println(f"Got value $d%.2f")

매개변수가 없는 함수

def hi() = "hi"
hi()
// hi

함수 호출 시 빈괄호를 붙이거나 안 붙일 수 있다.

관례 상 메소드에 부수 효과가 있다면 괄호를 넣고, 없다면 괄호를 사용하지 않는다.

def hi = "hi"
hi() // compile error
hi // hi

함수를 매개변수 괄호 없이 정의할 수 있다. 해당 경우에는 빈괄호를 붙여서 호출할 수 없다.

표현식 블록 매개변수

def formatEuro(amt: Double) = f"$amt%.2f euro"
formatEuro {
  val rate = 1.32
  0.235 + 0.7123 + rate * 5.32
}

단일 매개변수일 때 괄호를 중괄호로 바꾸고 표현식 블록을 매개변수로 사용할 수 있다. 함수 호출 시 표현식을 평가한 후, 반환값을 함수 매개변수로 사용한다.

계산된 값을 함수로 전달해야 할 때 지역 변수에 저장할 필요 없이 넘길 수 있다.

재귀 함수

꼬리 재귀

재귀적 호출이 새로운 스택 공간을 생성하지 않는 대신 현행 함수의 스택 공간을 사용하도록 최적화됨. 마지막 문장이 재귀적 호출이여야 한다

@tailrec annotation : 표시된 함수가 꼬리 재귀로 최적화 될 수 없다면 컴파일 에러 발생시킨다.

def boom(x:Int) : Int = 
    if (x == 0) 1
    else bool(x - 1)

@annotation.tailrec
def boom(x:Int) : Int = 
    if (x == 0) 1
    else bool(x - 1) + 1 
// 마지막 문장이 재귀적 호출만 있지 않다. + 1을 추가로 하고 있어 compile error

@annotation.tailrec
val funValue = nestedFum _
def nestedFum(x :Int) : Unit = 
    if (x != 0) {println(x); funValue(x - 1)}  
// 함수 값을 통하는 중간 경로가 있어 재귀적 호출이라고 볼 수 없다. compile error

중첩 함수

함수(외부) 내에 다른 함수(내부) 정의. 내부 함수는 외부 함수 scope에서만 사용할 수 있다.

메소드로 생각하면 scope을 class 내 -> 해당 method 내로 옮기는 효과가 있다.

def max(a: Int, b: Int, c: Int) = {
  def max(x: Int, y: Int) = if (x > y) x else y
  max(a, max(b, c))
}

이름 붙인 매개변수

매개변수 목록에 정해진 순서와 다른 순서로 함수에 인자 전달 가능

def speed(distance:Float, time:Float) = distance / time
speed(time = 10, distance = 100)

default 매개변수 값

default 값을 지정해 놓으면 함수 호출 시 해당 인자 생략 가능, 이름 붙인 인자와 같이 쓰면 유용함

기본값을 갖는 매개변수가 필수 매개변수 다음에 오도록 함수 매개변수를 구조화해야

def printTime(out: java.io.PrintStream = Console.out, divisor:Int = 1) = out.println("time = " + System.CurrentTimeMillis() / divisor)
printTime(out = Console.err)
printTime(divisor = 1000)

def greet(name: String, prefix: String = "") = s"$prefix$name"
greet("Ola")

가변 매개변수

정해지지 않은 개수의 입력 인수들로 함수를 정의

함수의 마지막 파라미터만 지정 가능, 함수 내에서 해당 타입의 배열로 취급됨

def echo(args: String *) = for(arg <- args) println(arg)
echo("AA")
echo("AA", "BB")

커링

타입 매개변수

매개 변수 또는 반환 값 등에 사용될 타입을 지시한다 (타입을 매개변수화)

def identity[A](a: A) : A = a
val s: String = identity[String]("Hello")
val s: String = identity("Hello") // 함수에 전달하는 리터럴 값이나 함수의 반환 값을 할당한 값의 타입을 통해 타입 추론 가능, 타입 매개변수 생략 가능

메소드

클래스에서 정의된 함수로 그 객체의 데이터에 동작. <인스턴스>.<메소드>로 호출

스칼라에서는 모든 것이 객체(int, double 등도)이므로 산술 연산자도 해당 객체 내의 메소드로 정의되어 있다.

객체와 메소드, 그리고 매개변수를 공백으로 구분하는 연산자 호출법으로 메소드 호출이 가능하므로 연산자 메소드들을 연산자처럼 보이게 할 수 있다.

2 + 3  // compiler가 아래 모양으로 변경
2.+(3)

d compare 18.0 // 모든 메소드에 사용 가능
"abcde" substring (1, 2) // 매개 변수가 여러 개면 괄호 이용

연산자 우선순위는 method 첫 글자를 보고 우선순위 결정. a +++ b * c(+++, * 는 메소드 이름)일 경우 a +++ (b *** c)로 취급(*가 +보다 우선순위 높음)

한가지 예외, =로 끝나는 메소드는 =(할당 연산자)와 동급으로 취급해 우선순위 제일 낮게 취급. x = y + 1일 경우 x = (y + 1)로 취급(가 +보다 우선순위 높지만. =는 할당연산자로 분류)

가독성 있는 함수 작성하기

  • 짧고 이해하기 쉬운 이름 정하기. 하고자 하는 일을 합리적으로 요약할 수 있는 이름.
  • 복잡한 함수를 작고 단순한 함수로 나누기
  • 함수 내에 세부사항과 맥락에 대한 설명, 잠재적인 문제점, TODO등을 주석으로 표현하기.
  • ScalaDoc 헤더 추가
Comments