메소드는 일반적인 객체의 멤버 함수를 의미합니다.

지역 함수는 함수 안에 정의된 함수를 의미하며 이 지역 함수는 지역 함수를 감싸고 있는 블록 내에서만 접근 가능합니다.

스칼라는 기본적으로 복잡한 함수를 다수의 작은 함수로 나눠야 하는 설계 원칙을 가지고 있습니다. 그래서 도우미 함수가 다수 존재합니다.

도우미 함수는 클래스 외부에서 사용하지 않고 클래스 내부에서만 사용되는 함수입니다. 도우미 함수는 외부에 노출될 필요가 없기 때문에 private 키워드를 사용하거나 지역 함수를 사용하게 됩니다.

지역 함수는 지역 함수를 감싸고 있는 블록 내에서만 접근 가능하기 때문입니다.

코드를 보면 processLine 함수가 processFile 안에 정의되어 있습니다. 따라서 processLine지역함수가 되고 processFile 블록 내에서만 호출 가능해 외부의 접근을  차단합니다.

그리고 변수의 스코프에 따라 processFile의 인자 filenamewidthprocessLine에서 사용가능 하기 때문에 processLine에서 별도로 인자 값을 받을 필요가 없습니다. 그래서 오른쪽 코드와 같이 바꿀 수 있습니다.

 

스칼라는 1급 계층 함수를 제공합니다. 1급 계층 함수는 함수를 정의하고 호출할 뿐만 아니라 이름 없이 리터럴로 표기해 값처럼 주고 받을 수 있습니다.

값처럼 주고 받을 수 있기 때문에 함수 반환 값, 인자로 전달 가능하고 특정 변수에 담을 수도 있습니다.

여기서 리터럴은 예와 같이 함수를 표현한 소스코드 자체를 의미하고 이런 함수 리터럴은 FunctionN 클래스로 컴파일됩니다. 만약 인자가 하나라면 Function1, 인자가 두 개라면 Function2 클래스로 컴파일 됩니다.

이런 컴파일된 클래스를 실행 시점에 인스턴스화해 객체로 만들면 이를 함수 값이라고 부릅니다.

그래서 아래 예처럼 함수 값은 인스턴스화된 객체이기 때문에 변수에 저장할 수도 있고 함수 값은 함수이기도 하기에 함수를 호출하는 방식으로 함수를 호출할 수 있습니다.

또한 var 변수이기에 다른 함수 값으로 변경 가능하고 함수 리터럴 내부가 여러 줄을 표현된다면 중괄호로 감싸면 됩니다.

 

 

함수 리터럴을 간단하게 만드는 방법을 살펴보겠습니다.

위 예는 1,2,3,4,5,6 원소를 가지는 리스트를 정의하고 foreach를 통해 numbers의 요소를 순회하면서 print 하는 예입니다.

foreach 내부에 함수 리터럴이 사용됐습니다.

이 함수 리터럴의 인자 타입을 컴파일러가 추론할 수 있다면 인자 타입을 제거할 수 있습니다. 예에서는 numbers의 원소가 이미 Int인 것을 numbers를 정의할 때 알 수 있습니다.

그래서 컴파일러는 foreach 내부 x 원소가 Int인 것을 알기 때문에 타입을 명시해주지 않아도 타입을 추론 가능합니다. 그래서 인자 타입 제거가 가능합니다.

또한 타입 추론이 이뤄진 인자를 둘러싼 괄호를 제거해서 더 간단한게 나타낼 수 있습니다.

 

위치 표시자 문법은 기존 함수 리터럴을 더 간결하게 만들기 위해 사용합니다.

코드와 같이 “x=>” 화살표를 생략하고 “x=>”를 생략하게 되면 foreach 함수 리터럴 내부에서 사용되는 각 원소를 식별할 수 있는 식별자가 없기 때문에 “_”를 사용해 함수 호출 시 전달받은 인자가 채워지도록 할 수 있습니다.

이 밑줄을 위치 표시자라고 하고 위치 표시자는 컴파일러가 인자의 타입 정보를 찾지 못할 경우 인자의 타입을 명시해야 합니다.

“_+_” 함수 값을 정의하면 다음과 같이 파라미터의 타입을 찾지 못한다고 에러가 납니다. 컴파일러에서 +메서드를 호출해야하는 데 무슨 객체의 +메소드를 호출해야할 지 모르기 때문입니다.

그래서 인자 타입 추론이 불가능하면 타입을 명시해줘야 합니다.

“_+_”를 보면 첫 번째 _에는 첫 번째 인자가 들어가고  두 번째 _에는 두 번째 인자가 들어가는 걸로 볼 때 “_”을 반복해서 사용하는 것은 하나의 인자를 반복해서 사용하는 게 아니라 여러 개의 인자라는 걸 의미합니다.

, 위치 표시자는 함수 내부에서 하나의 파라미터에 대해 여러 번이 아닌 한번만 사용 가능합니다.

 

 

부분 적용한 함수는 함수에 필요한 인자를 일부만 제공하거나 전혀 제공하지 않는 표현식을 의미합니다.

예를 보면 인자 세 개를 더하는 sum 메소드를 정의합니다. 그리고 왼쪽 코드와 같이 sum _ 을 해주면 b 변수에 a,b,c 3개의 인자를 요구하는 함수 값 객체가 만들어집니다.

b가 함수 값 객체이기 때문에 함수처럼 호출이 가능하고 3개의 인자를 요구하기 때문에 3개의 인자를 넣어 호출할 수 있습니다.

스칼라 컴파일러 내부적으로는 sum _ b에 할당될 때 3개의 인자를 요구하는 클래스 인스턴스를 만들고 b(1,2,3)과 같이 호출을 할 때 b.apply로 바꾸게 됩니다.

인자 일부만 적용하고 싶다면 오른쪽과 같이 제공하지 않는 인자에 위치표시자를 명시해 주면 됩니다.

 

이 부분 적용한 함수는 함수 값이기 때문에 반환이 가능합니다.

왼쪽 코드는 f 메소드의 지역 함수 g를 함수 값 객체로 반환하는 것인데 g만 적어주면 이 것은 메소드 이름을 가리키지 함수 값 객체를 가리키지 않기 때문에 이 때 부분 적용한 함수를 사용할 수 있습니다.

또한 함수가 필요한 시점이라면 “_”를 사용하지 않아도 됩니다.

 

“ (x:Int) => x+more함수 리터럴이 있을 때 x는 바운드 변수 more은 자유 변수라고 합니다. X는 주어진 함수 내에서만 의미가 있고 함수의 호출이 끝나면 x 변수는 사라집니다.

more는 함수 내부에서 사용되지만 외부에 정의되어 있어 함수 자체에만 의미를 부여하지 않습니다.

“ (x:Int) => x+1”  처럼 자유 변수가 없는 함수 리터럴은 닫힌 코드 조각이라 하고 자유 변수가 있는 함수 리터럴은 열린 함수 조각이라고 합니다.

이 열린 함수 조각에서는 함수 외부에서 정의된 자유 변수 값을 알아야 하는 데 이를 위해서 실행 시점에 자유 변수에 대한 바인딩을 캡처합니다. , 자유 변수의 실제 값이나 위치를 얻어서 자유 변수에 값을 지정해서 자유변수를 없앱니다.

, 열린 함수 조각을 자유 변수를 없애 닫힌 코드 조각처럼 닫는 다고 해서 자유 변수가 있는 열린 함수 조각을 클로저라고 합니다.

 

클로저는 자유변수 바인딩을 가지고 있기 때문에 자유 변수의 변화를 감지합니다.

클로저는 생성될 때 자유 변수를 바인딩합니다. 코드에서  more 변수는 makeIncreaser의 인자이기 때문에 호출 후 사라져서 실제 inc1() 과 같이 호출할 때 more 값이 없을 것 같지만

캡쳐된 자유 변수는 메소드의 스택이 아닌 힙에 배치되서 makeIncreaser 메소드보다 더 오래 살아남을 수 있습니다.

 

 

반복 파라미터는 함수의 마지막 파라미터를 반복 가능하다고 지정할 수 있어서 마지막 파라미터를 가변 인자처럼 사용 가능합니다.

반복 파라미터를 사용하려면 “*”을 마지막 인자의 타입 다음에 추가하면 됩니다.

예제의 “String*”Array[String]을 의미합니다.

그렇다고 String*의 인자에 Array[String] 변수를 전달하면 에러가 납니다. 배열을 반복 인자로 전달하기 위해서는 “: _*”를 추가해야 합니다.

 

 

재귀가 while 보다 성능이 떨어지는 이유는 재귀 함수가 호출될 때마다 함수에 대한 스택이 생성되어야 하고 재귀 함수 리턴 후 돌아가야할 함수의 스택 주소를 계속 저장해야 하기 때문입니다.

 

왼쪽 코드는 재귀 함수 호출 후 +1을 하기 때문에 꼬리 재귀가 아닙니다.

오류 내용을 보면 boom을 여러 번 호출합니다.

오른쪽 코드는 꼬리 재귀 함수인데 bang을 한 번만 호출하고 재귀 호출 하지 않는 걸 볼 수 있습니다.

꼬리 재귀는 동일한 함수를 직접 재귀 호출하는 경우에만 최적화를 수행합니다.

왼쪽과 같이 2개의 함수가 서로 번갈아가면서 호출하는 경우, 오른쪽과 같이 함수값을 호출하는 경우 꼬리 재귀 최적화가 이루어지지 않습니다.

If 표현식에 대해 살펴보겠습니다.

일반적인 자바와 같은 명령형 프로글밍에서는 왼쪽 코드와 같이 if 문을 사용합니다. Args가 비어있지 않다면 filenameargs(0)을 할당하고 비어 있다면 “default.txt” 가 됩니다.

이런 if문은 어쩔 수 없이 var을 사용해야 합니다. If 조건문에 따라서 변수 값을 바꿔야 하기 때문입니다.

하지만 스칼라는 제어 구문이 결과를 내놓는다는 특징이 있기 때문에 오른쪽과 같이 코드를 바꿀 수 있습니다.

만약 args가 비어있지 않다면 args(0)값을 내놓고 비어있다면 “default.txt” 값을 내놓습니다. If 표현식이 내놓은 값을 filenam에 바로 집어넣습니다.

이렇게 하면 filename에 값을 바로 넣는 것이기 때문에 filenameval로 정의 가능하여 부수효과를 없앨 수 있습니다.

그리고 이러한 표현식은 하나의 값을 무조건 내놓기 때문에 표현식과 값을 동등하게 봐 아래와 같이 println내부에 표현식을 집어넣을 수 있습니다.

 

While은 일반적으로 참인 조건동안 특정 작업을 수행하는 데 사용하고 어떤 리턴하는데 사용하지 않습니다. , 값을 내놓지 않기 때문에 표현식이라 하지 않고 루프라고 합니다.

그래서 while 루프의 결과 타입은 Unit 이라는 빈 값이 되겠습니다.

Unit 에 대해 알아보면 Unit메소드에서 리턴이 없을 때, var 변수에 값을 재할당할 때 반환됩니다. 반환된 Unit 값은 “()”가 됩니다.

왼쪽 코드를 보면 greet()라는 println을 수행하는 메소드의 리턴값이 Unit이고 값으로 표현할 때는 빈괄호가 되는 것을 알수 있습니다.

오른쪽 코드는 특정 파일의 line빈문자열이 될 때까지 한 줄씩 읽는 것을 나타냅니다.

한 줄씩 읽는 코드를 보면 line변수에 읽은 문자열을 재할당하는 때 이 때 Unit 값인 ()가 반환됩니다. 따라서 “line=readLine()” 결과가 항상 ()이므로 “”와 같을 수가 없어 루프가 무한 반복됩니다.

다시 while루프에 대해서 보면 while 루프는 결과가 특정 값이 아니기 때문에 함수형 언어에서는 이를 종종 제외합니다.

while루프를 제외 이유는 작업을 통해 프로그램에 영향을 주기 위함인데  while 결과가 값이 아니라면 프로그램에 영향을 주기 위해 I/O를 수행하거나 외부 var 변수를 갱신해야해 부수효과를 일으키기 떄문입니다.

하지만 스칼라는 while을 사용한 코딩 방법의 가독성이 더 뛰어난 경우가 있기 때문에 while을 없애지 않았습니다.

따라서 while은 부수효과를 일으킬 가능성이 매우 높기 떄문에 사용할 때 var을 최소화하고 꼭 while을 사용해야 하는지 의심해야 합니다.

 

 

for 표현식 여러 기능에 대해서 살펴보겠습니다.

스칼라의 for 표현식은 배열이 아닌 어떤 컬렉션에도 iteration이 적용됩니다. <- 사용 iteration 합니다. <- 문법을 제너레이터라고 하는 데 제너레이터에 의해 생성되는 하나의 원소는 모두 val 타입이 됩니다.

또한 Range 타입이 존재하는 데 아래 코드와 같이 to를 사용하면 1,2,3,4 순회할 수 있고 until을 사용하면 마지막을 제외한 1,2,3 순회하게 됩니다.

위와 같은 기능들이 있기 때문에 아래의 코드는 스칼라에서 바람직하지 않습니다. To 대신 until을 사용해서 -1을 제거해 간단하게 보이게 할 수도 있고 맨 위 코드와 같이 직접 iteration 할 수 있기 때문입니다.

 

for를 수행하는 과정에서 전체 컬렉션 중에 조건에 맞는 원소들만 사용하고 싶을 경우가 있습니다. 이 때 필터를 사용하면 됩니다.

일반적인 명령형 프로그래밍에서는 맨 위와 같이 for문 아래에 if문을 넣으면 되지만 함수형에서는 for조건절 내부에 if문을 삽입하면 됩니다.

만약 여러 개 필터를 추가하고 싶을 때는 아래 코드와 같이 하면 됩니다.

 

2개의 루프를 중첩하고 싶다면 forgenerator 부분에 <-를 추가하면 됩니다.

여기서 각 <- 세미콜론으로 구분을 했는 데 소괄호가 아닌 대괄호를 사용하며 컴파일러에서 세미콜론을 추론해 세미콜론을 생략할 수 있습니다.

 

 

왼쪽 코드는 line.trim 이라는 코드가 반복됩니다. trim을 한 번만 계산하고 싶다면 오른쪽 코드와 같이 for문 내부에 변수를 만들 수 있습니다.

trimmed 변수가 val인지 var인지 명시되지 않았는 데 자동으로 val로 선언됩니다.

 

 

앞에서 보았던 for문은 컬렉션을 iteration하면서 조작만 하고 결과는 무시했습니다. 하지만 iteration의 매 반복 단계의 결과를 저장할 수 있습니다.

이 때 for 표현 식 뒤 그리고 본문 앞에 yield 키워드를 사용하면 됩니다.

왼쪽 예는 파일 리스트를 순회하면서 “.scala로 끝나는 파일들을 반환합니다. 여기서 리턴값은 Array[File]이 됩니다.

오른 쪽은 “.scala로 끝나는 파일들의 파일 내용을 라인별로 순회하면서 for 문자열이 들어간 라인의 길이를 리턴합니다. 여기서 리턴 값은 Array[Int]가 되겠습니다.

또한 아래 처럼 yield 뒤에 본문에 중괄호를 써서 특정 로직을 명시할 수도 있습니다.

 

 

스칼라에서 try는 자바의 try와 비슷합니다. 예외를 발생시킬 때는 위 코드와 같이 “throw new Exception”으로 예외를 던지게 됩니다.

코드를 보면 n2로 나눌 때 예외가 발생할 때 예외를 던지는 데 이때 예외는 Nothing이라는 타입을 반환합니다. 그래서 halfNothing 타입 값이 들어갑니다.

그러나 예외가 나면 이 로직을 호출한 쪽으로 제어흐름이 넘어가기 때문에 half 변수를 다시 사용할 일이 없어 Nothing 타입에 대해 신경 쓸 필요가 없습니다.

단순히 throw를 자유롭게 쓰기 위한 기술적 장치일 뿐입니다.

Throw로 발생한 예외를 잡기 위해서는 자바와 똑같이 catch를 사용합니다. 다른 점은 자바는 catch 구문마다 파라미터에 throws로 자신이 잡을 예외 종류를 명시합니다.

하지만 스칼라는 하나의 catch 절 안에 case 구문을 이용해 예외를 잡게 됩니다.

 

 

Finally 절은 표현식의 결과가 어떻든 특정 코드를 반드시 수행하고 싶을 경우 finally 절로 감쌀 수 있습니다.

스칼라에서는 finally를 파일, 소켓, 데이터 베이스를 사용 후 자원을 확실히 닫기 위해 사용합니다.

다른 표현식과 마찬가지로 try-catch-finally의 결과도 값입니다.

왼쪽 코드를 보면 예외가 나지 않을 때는 path URL 객체를 반환하고 예외가 발생했을 때는 다른 url리턴하는 걸 볼 수 있습니다.

Finally 의 경우 독특합니다. 스칼라에서 finally는 무조건 자원을 닫는 정리 작업만 수행해야 합니다.

오른쪽 코드와 같이 finally 에서 명시적으로 return을 쓰면 finally에서 값을 내놓지만 return을 사용하지 않으면 finally에서 값을 내놓지 않습니다.

, finally는 결과값을 만들어 내기보다는 파일을 닫거나 정리 작업을 하는 등의 부수효과를 제공하는 방법으로 생각해야 합니다.

 

 

Match 표현식은 자바의 switch문과 유사합니다. 코드를 보면 match 표현식 앞에 비교할 변수를 적습니다. 그리고 각 case 별로 firstArg 변수가 “salt” 인지 확인합니다.

스칼라의 match 표현식이 자바의 switch와 다른 점은 case 문에 타입에 상관없이 상수를 사용할 수가 있습니다.

그리고 break문이 없는데 내부적으로 break문이 암묵적으로 있어서 break 문 없어도 다음 case 문으로 넘어가지 않습니다.

“_”는 디폴트 케이스에 해당합니다.

그리고 match도 역시 표현식이기 때문에 코드와 같이 값을 내놓는 것을 볼 수 있습니다.

 

 

스칼라는 break문과 continue 문이 없습니다. 왜냐하면 스칼라는 함수 자체가 값을 가지게 되는 데 breakcontinu를 사용하게 되면 어떤 경우에서는 값을 가지지 않게 되기 때문입니다.

그래서 스칼라에서는 continu문을 if문으로 , break문을 Boolean 변수로 대체하라고 합니다. 또는 breakcontinu문은 while 구문 안에서 많이 사용되는 데 이를 재귀함수로 바꾸라고 합니다.

 

 

 

변수 스코프는 자바와 동일합니다. 안쪽 스코프에서 정의된 변수는 외부에서 사용할 수 없고 외부에서 정의된 변수는 내부에서 사용할 수 있습니다.

자바와 다른 점은 자바는 안쪽 스코프와 바깥쪽 스코프에서 동일한 변수 이름을 재정의한다면 컴파일 오류가 납니다. 그런데 스칼라에서는 이게 가능합니다.

예에서 println(a)를 하게 되면 가장 가까운 스코프의 변수를 출력하게 되 2가 출력되고 이런 것을 안쪽 변수가 바깥 스코프의 변수를 가렸다 라고 표현합니다.

이와 같이 동일한 변수에 값을 재정의하는 것을 스칼라 인터프리터를 통해 봤었는 데 마음대로 재정의 할 수 있는 이유는 오른쪽과 같이 입력한 각 라인마다 안쪽에 새로운 스코프를 만들기 때문입니다.

그래서 재정의가 가능하고 a에는 외부 스코프의 변수를 가리고 최종적으로 3이 들어가 출력됩니다.

 

+ Recent posts