다음 코드에서s의 타입은 String입니다.
val s = "Hello"
val, var, def로 이름을 선언하고, 그 뒤에:를 붙여, 다음처럼 타입을 지정 할 수 있습니다.
val s: String = "Hello"
다음 ls의 타입은 List[Int] 입니다.
val ls: List[Int] = List(1, 2, 3)
다음은 문자열을 파라미터로 받아서, 아무 것도 하지 않고 그대로 리턴하는 메소입니다.
def f(s: String) = s
메소드는 함수형 프로그래밍의 함수와는 차이가 있습니다. 그래서 함수의 타입에 대해서 설명하기 위해서 위 메소드를 다음처럼 함수 리터럴로 변환시켜 보겠습니다.
val f = (s: String) => s
메소드 표현 보다는 약간 익숙하지 않습니다. 하지만 어떻게 바뀌는지 잘 눈여겨 보시길 바랍니다.
함수에도 타입이 있다는 개념이 생소할지 모르지만, 알고보면 간단합니다. (파라미터 타입 =>리턴 타입)으로 함수의 타입이 결정됩니다.
스칼라는 정적타입언어이고, 함수 역시 다른 값과 마찬가지로 타입이 존재합니다. 위 함수는 String을 인자로 받고 String을 내놓으니 String => String이 타입이 되는 것이지요.
그래서, 함수 표현서 이름 뒤에:를 추가하고 그 뒤에, 명시적으로 타입을 나타내는 부분(String => String)만 추가하면 다음과 같습니다. 이전에 생략이 가능했던 이유는 스칼라의 타입추론(type inference) 때문입니다.
val f: (String => String) = (s: String) => s
위 코드 처럼 val f타입을 명시적으로 지정해 준 경우도 마찬가지로, 타입 추론으로 인하여 함수의 파라미터에 해당하는(s: String)의 타입 부분을 생략 할 수 있습니다. 익숙해지기 전 까지는 이렇게 다르게 표현되는 점 때문에 함수리터럴을 찬찬히 살펴봐야 파악 할 수 있습니다.
val f: (String => String) = s => s
String => String의 타입은 그 자체로 존재하는 대신, 내부적으로Function1[String, String]이라는 타입으로 표현됩니다. ScalaIDE의 worksheet이나 Repl을 활용하면 String => String = <function1>라는 출력을 보실 수 있습니다.
Function1[String, String]에서 Function뒤의 1이라는 숫자는 파라미터의 갯수를 나타냅니다. 그리고 앞의 String은 함수가 전달 받는 인자의 타입입니다. 그리고 마지막의 String은 리턴 타입니다.
이번에는 Int를 입력 받고 String을 리턴하는 함수를 보겠습니다.
val f = (i: Int) => i.toString
위의 함수 타입은 (Int => String) 타입이겠죠. 함수의 파라미터에서 Int형을 전달 받고 String으로 변환해서 리턴하니까요. 마찬가지로 내부적으로는 Function1[Int, String] 타입으로 표현됩니다.
val f = (s1: String, s2: String) => s1 + ", " + s2
위 코드 처럼 파라미터를 두 개 받는 함수의 타입은 (String, String) => String이 되겠죠. 그리고, 파라미터의 타입이 두 개이므로 내부적으로는 Function2로 표현됩니다. 좀 더 자세히 보면Function2[String, String, String] 처럼 표현됩니다. 첫 번재와 두 번째의 String은 각각 함수의 첫 번째, 두 번째 파라미터의 타입이며, 마지막String은 리턴값의 타입을 나타냅니다.
그럼, 실제 위와 같은 코드를 Function2타입을 사용해서 정의해보겠습니다.
val f2 = new Function2[String, String, String] {
def apply(a: String, b: String) = {
a + ", " + b
}
}
f2("Foo", "Bar") //> Foo, Bar
일반적으로 익명클래스를 정의하는 것과 거의 비슷하죠. 실제로도 함수리터럴을 정의하면 익명클래스를 정의하는 것과 거의 가은 방식으로 작동합니다. 그리고 def apply부분이 보이는데요. 함수의 이름과 ()만 사용해서 파라미터를 전달하면, 알아서 apply부분이 호출됩니다. 위와 같이 직접 Function2를 사용해서 함수를 선언 할 일은 없지만, apply로 전달되는 메커니즘은 기억해 두세요.
앗 감사합니다.
Function1[ (TypeA, TypeB), TypeC ] 는 Function1[ Tuple2[TypeA, Typeb], TypeC ]
로 표현됩니다. 저도 처음에 자주 헷갈렸던 것인데, 꼭 짚어주셨네요.
Function.tupled는 이야기 했었는데, untupled가 있는건 지금 처음 알았습니다.
def add(x: (Int, Int)) = {
x._1 + x._2
}
val add2 = Function.untupled(add _)
해보니까 되네요~ ㅎㅎ
add( x: (Int, Int) )처럼 튜플을 인자로 받는 경우는 원래 add((1, 2)) 로 작성해야 하지만, add(1, 2)처럼 해 줘도 자동으로 튜플링해서 전달해주는 부분도 생각났네요.
2) 번의 경우도, (A => B) 를 상속받는게, Function1을 상속받는 것이고, 데코레이터패턴을 예로 들어주시니, 이해하기 쉬운 것 같습니다.
앞으로도 틈틈히 첨언 부탁드리겠습니다~ 감사합니다~ ^ㅡ^