제 블로그의 모든 글은 IMHO로 쓴 것입니다. 서로에 대한 존중을 담은 덧글을 남겨 소통을 하신다면 더 좋은 글로 발전이 될 수 있을 것 같습니다. 존중이 담기지 않은 덧글은 언제든 삭제될 수 있습니다. 감사합니다:)
—
함수 -> 재활용 가능한 “표현식” -> 주어진 입력으로 계산하는 것 이외 side effect 가 없다
순수 함수의 조건
- 하나 또는 그 이상의 입력 매개변수를 가진다
- 입력 매개변수만을 가지고 계산을 수행
- 값 반환
- 동일 입력에 대해 항상 같은값 반환
- 함수 외부의 어떤 데이터를 사용하거나 영향 x
- 함수 외부 데이터 영향 x
순수 함수의 조건에 맞춰서 개발하면 good…
함수 정의
def multiplier(x:Int, y:Int):Int = {x * y}
def -> 예약어 multiplier -> 함수명 (x:Int, y:Int) -> 매개변수 타입은 추론하지 않으므로 지정해야 한다 :Int -> 결과 타입을 지정(결과 타입은 추론이 가능하므로 생략할 수 있다, 재귀 함수면 생략 불가능)
- 함수 본문은 표현식 또는 표현식 블록으로 구성되어 있으며, 마지막 줄이 함수의 반환값이 된다
프로시저
- 반환값을 가지지 않는 함수
- 문장(println()호출 등)으로 끝나는 함수.
- Unit 타입으로 추론된다.
def log(d: Double) = println(f"Got value $d%.2f")
//매개변수가 없는 함수
def hi() = "hi"
hi()
// hi
- 함수 호출 시 빈괄호를 붙이거나 안 붙일 수 있다.
- 관례 상 메소드에 side effect 가 있다면 괄호를 넣고, 없다면 괄호를 사용하지 않는다.
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
- 표시된 함수가 tail recursion으로 최적화 될 수 없다면 컴파일 에러 발생시킨다.
@annotation.tailrec
def boom(x:Int) : Int =
if (x == 0) 1
else bool(x - 1) + 1
// 마지막 문장이 재귀적 호출만 있지 않다. + 1을 추가로 하고 있어 compile error
@annotation.tailrec
def pow(x: int, n: int): Log = {
if (n < 1) 1
else x * power(x, n-1)
}
// x 가 있어서 곱셈...compile error
@annotation.tailrec
val funValue = nestedFum _
def nestedFum(x :Int) : Unit =
if (x != 0) {println(x); funValue(x - 1)}
// 함수 값을 통하는 중간 경로가 있어 재귀적 호출이라고 볼 수 없다. compile error
@annotation.tailrec
def boom(x:Int) : Int =
if (x == 0) 1
else bool(x - 1)
// 그냥 진짜 함수만 있어야 한다.
중첩 함수
함수(외부) 내에 다른 함수(내부) 정의. 내부 함수는 외부 함수 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)
// 보다는
def greet(name: String, prefix: String = "") = s"$prefix$name"
가변 매개변수
정해지지 않은 개수의 입력 인수들로 함수를 정의
함수의 마지막 파라미터만 지정 가능, 함수 내에서 해당 타입의 배열로 취급됨
def echo(args: String *) = for(arg <- args) println(arg)
echo("AA")
echo("AA", "BB")
커링
def test(a: A, b: B) = ...
->
def test(a: A)(b: B) = ...
타입 매개변수- 제너릭!?
→ 자바와 비교 추가시 굿!
타입을 매개변수화
난 별루… 이지만 타입매개변수를 사용하면 타입들이 고정되어 있던 상태에서 함수 호출자에 의해 설정될 수 있는 상태로 바뀜에 따라 함수의 유연성과 재사용성이 높아진다고 한다.
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 헤더 추가
/**
* scaladoc헤더
*
*/
def safeTrim(s: String): String = {
if (s == null) return null
s.trim()
}