이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한   Programming in Scala (2판)   

을 참고로 하여 정리할 예정입니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다.


List

 
특성
 

스칼라의 배열이 값을 변경 할 수 있는 순서가 정해진 시퀀스라면 스칼라의 리스트는 기본적으로  값을 변경 할 수 없는 시퀀스입니다. 

함수형 스타일이라는것은 메소드 내에서 절대로 부수효과가 일어나면 안되는, 그래서 더 신뢰할 수 있고 재사용하기 쉬운 코드를 만드는게 주 목적이라 , 그 목적에 적합한 콜렉션이라 할 수 있습니다.
Linked List 식으로 구현 되 있으므로 head / tail  중간 삽입같은게 원할합니다. 
 
생성 

배열 생성과 비슷합니다.

val list = List(1,2,3)   // new 와 [Int] 가 생략되었습니다. apply 라는 팩토리 함수가 암시적으로 호출.
아래 처럼 다양하게 만들 수 있습니다. 

val x = List.range(1, 10) // 범위로 List(1, 2, 3, 4, 5, 6, 7, 8, 9)

val x = List.fill(3)("foo") // List(foo, foo, foo)
List.tabulate(5)(n => n * n) // List(0, 1, 4, 9, 16)

 

자바)  
List b = new List;  <-- 안된다 . 인터페이스임
String a[] = new String[2]; 
List b = Arrays.asList(a); 
List<String> list =new ArrayList<String>();
List<String> list =new LinkedList<String>();  

코틀린) 
val z = List()  <-- 안된다 . 인터페이스임
val a = listOf("hi", "bye")  
val b = List<String>(2, {it -> it.toString()})  
val c = mutableListOf<String>()

 

 

원소 가져오기 

val a = List(1,2,3) 

a(2)  

  

두개의 리스트 합치기 

두개의 리스트를 합쳐보겠습니다.

val a = List(1,2)

val b = List(3,4)

val c = a ::: b

이렇게 합니다.

:::  는 두개의 리스트를 합쳐주는 메소드 입니다.

val d = a ++ b  이렇게 할 수도 있습니다.  ( ::: 는 오로지 리스트에서만 사용) 

Scala list concatenation, ::: vs ++

 

리스트 앞에 요소 추가 

 

그럼 앞에다가 요소하나를 붙여주는 메소드는 무엇일까요?

List(1,2) 앞에다가 0 을 붙여서  (0,1,2) 로 만드는 방법 말이죠.

그건  :: 입니다.  콜론이 2개짜리네요.

근데 여기서 생각해 볼것은 서두에 List 는 변경 불가능하다고 했는데 앞에 숫자를 붙히다니? 

무슨 소리하는지 의심스러울거 같은데요.

예를 한번 봅시다.

val a = List(1,2)

val b = 0 :: a

이건데요. 

네 a 를 바꾼게 아니었습니다.  a 앞에 0 을 추가한 또 다른 b 라는 List 를 만든거에요.

즉 무엇을 변경해서 쓰려면, 기존것은 냅두고 기존것을 이용해서 새것을 만들어서 쓰라는 말입니다.

또 궁금한게 있을거 같은데요.

도대체 0 :: a 를 하는데 어떻게 a 앞에 0 이 들어가게 되는지 말이죠.

그 이유는 

:: 메소드는 0 의 메소드가 아니라  a 의 메소드입니다. 근데 앞에 있는 이유는 

그냥 규칙입니다. -.-;; 

우리의 마틴오더스키씨는 아주 많은걸  자신이 만든 언어에 쑤셔 넣었습니다.

그 규칙은 이름이 콜론(:) 으로 끝나는 메소드는 오른쪽 피연산자의 것으로 호출한다. 라고 합니다.

따라서

0 :: a  는  a.::(0) 이 되는 것입니다.

 

리스트 뒤에 요소 추가 

 

그럼 뒤에 추가하는것은 무엇일까요?

:+  입니다.

예를들어  

val a = List(1,2)

val b = a :+ 2

이런거죠.

하지만 웬간하면 이걸 쓰지마세요.

뒤에 추가하는 연산은 리스트의 길이만큼 오래 걸린다고 합니다.

이걸 효율적으로 하려면

일단 리스트를 뒤집고, 앞에다가 원소를 추가한후에 다시 뒤집으세요.

(4 :: List(1,2,3).reverse).reverse

- 새 리스트로 추가한다.

List(1,2,3) ::: List(4)

 

입니다.

 

 

List 의 다양한 메소드들 

 

자 이런 변경 불가능한 List 를 사용해서 편하게 작업할 수 있는 방법들이 무지 많습니다.

문제는 편해지려면 이거 공부하고 외워야한다는거죠.  러닝커브가 올라갑니다. ;;

대표적인 몇가지만 살펴보도록 하겠습니다.


// 빈리스트

List()   

 

// 두번째 인덱스 원소 얻기 

val a = List(1,2,3) 

a(2)

 

// 두번째 원소 까지  제거

val a = List(1,2,3) 

val b = a.drop(2) 

결과 : List(3)  

눈여겨 볼것은 자체의  원소를 제거한게 아니라,  원소 제거한 새로운 리스트를 반환합니다. 

 

// 요소중 길이가 3인것의 개수를 센다

val a = List ("hello", "world", "boy") 

a.count(s=> s.length == 3)   

s=> s.length == 3 은 람다식이죠? 인자가 s 인 함수란 야그입니다.  다음과 같아요

bool  func ( s ) {

  return s.length == 3

}

 

// 리스트의 각 원소를 변경하여 새 리스트를 반환합니다. 여기선 각 문자열 뒤에 X 를 붙힙니다.

val a = List ("hello", "world", "boy") 

val b = a.map(s => s + "X") 

자 다양한 메소드를 보았는데요. 이거 말고도 더 있긴합니다. 근데 여기서 생각해 봐야할것은 메소드가 많구나~~ 이게 아닙니다.
이런 메소드를 사용함으로써 var 를 안쓰게 됬다는겁니다. var 은 변경 가능한 변수를 만들때 씁니다.
예를들어 
위에서 count 같은 경우 대략 함수를 만들어 보면 

def count( all : Seq[String] ):Int = { 
   var temp = 0  
   for ( one <-  all ) {       
    if ( one.length == 4 )
         
       temp = temp + 1
   
    }
 
    temp
}

이렇게 되잖습니까? 위에 보면 var 가 사용되었네요. 스칼라에서는 var 를 사용하지 말도록 합시다!!! (마틴오더스키는 var, val 둘다 만들어서 알아서 적재적소에 쓰이길 바랬지만 , 제 글을 보시는 분들은 절대로 var 를 안쓰는 방향으로 '만' 생각하자구요. ) 

 

* 재귀를 활용한 count 함수  

 def count(list: Seq[String]): Int = {      
 	@tailrec def count(value: Int, remaining: Seq[String]): Int = {       
      remaining match {         
      	case head :: tail if head.length == 4 => count(value + 1, tail)         
        case head :: tail => count(value, tail)         
        case Nil => value       
        }     
     }      
     count(0, list)   
 }

이렇게 count 를 var 없이 구현 할 수 있습니다. 꼬리재귀라는것을 사용한것인데요

순수함수형 언어에서는 재귀를 주로 사용한다네요. 공재귀로 분해해보는 연습을 해야 합니다. 이거보고 스칼라는 쓸 때없이 복잡한거네라고 생각하지마시구요 여기서는 그냥 이런게 있구나 하고 지나치시고 나중에 다시 차근차근 쉽게쉽게 살펴보자구요. 분명히 먼가 좋으니깐 저렇게 쓰는거다라고 생각하시고 넘어갑시다.

 
 

마지막 

List 의  짜증나는 규칙을 하나 억지로 외워봅시다.

val a = 1 :: 2 :: 3 :: Nil

는 List(1,2,3) 을 만들게 됩니다.

저 Nil 은 뭘까요? 

리스트 끝에 Nil 이 필요로 하는 이유는 :: 메소드가 List 클래스의 멤버이기 때문이랍니다.

만약 1 :: 2 :: 3 만 사용했다고 치면 , 마지막의 3 이 Int 형이라서 :: 메소드가 없기때문에

그냥 꽝이 되버리는 반면

마지막에 Nil 을 넣어주게되면 Nil 은 List 의 멤버이기때문에 타입을 추론해서 List 로 만들어 줍니다. 

'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (2) - Array  (0) 2016.06.13
스칼라 강좌 (1 ) - 소개  (0) 2016.06.13

이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한   Programming in Scala (2판)   
을 참고로 하여 정리할 예정입니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다

 


Array

 
생성 

스칼라에서 객체로 만들때는 new 를 사용합니다. 다음과 같죠
val a = new  Array[String](2)  // String 타입이 2개 담길수 있는 배열의 객체를 만들었네요.
자 배열에 값은 어떻게 넣을까요? 
a(0) = "hello"
a(1) = "world"

요렇게 넣으면 됩니다.  

여기서 눈여겨 볼것은 일단 val 로 선언했는데 값을 바꾼겁니다. 이건 전에 얘기했다시피 변수를 재 할당 할 수 없는거지, 변수가 가르키는 객체 안의 내용은 여전히 변경가능하다는거죠. 

- 자바에서는 String a[] = new String[2] 이렇게 만듭니다. 
- 코틀린에서는 
var array1 : Array<String?> = emptyArray()
var array2: Array<String?> = arrayOfNulls(4)
var array3 = arrayOf<String>("Mashroom", "Kitkat", "Oreo", "Lolipop")
var array4 = Array(2, {""}))

자 그럼 값을 출력해볼까요?

 

값 가져오기

print(a(0)) 

이렇게 하면 값을 꺼내와서 출력에 사용합니다.

() 를 통해서 접근했는데요. 어떻게 가능 할까요??

스칼라에서는 변수 뒤에 하나 이상의 값을 괄호로 둘러싸서 호출하면 

그 변수의 apply() 메소드를 호출하는것으로 바꾸어줍니다.

즉 a.apply(0) 이렇게 변경된다는거죠. 

a(0) 는 a.apply(0) 입니다. 배열객체에서 apply 는 해당 인덱스에 해당하는 값을 꺼내오는 겁니다.

이런 apply 는 배열객체에만 있는건 아닙니다. 

 

값 넣기  

그럼 값을 짚어넣는건 어떻게 된걸까요?

a(0) = "hello"

이거 말입니다.  이것도 역시 생략된것인데요.

괄호로 둘러싼 인자들이 있는 표현식에 할당이 되면 컴파일러는 

a.update(0, 'hello') 이렇게 바꾸어줍니다.  

즉 값을 가져오거나 넣을때 사용하는 () 는 좀 편하게 쓰라고 스칼라 창시자가 만든 장치입니다.

실상은 메소드 호출이란 야그죠. 

 

배열을 만드는 더 간편한 방법 

val a = Array("hello", "world") 

이렇게 만들 수 있습니다.

new 도 생략되었고 [String] 도 생략되었습니다.

[String]  생략

초기화할때 인자로 String 이 들어가기때문에 타입을 추론한겁니다.

new  생략

암시적으로 apply 라는 이름의 팩토리 메소드를 호출해서 객체를 만들어줍니다.

Array.apply("hello", "world") 원랜 이런거란거죠.

스칼라 창시자는 참 별걸 다 신경써서 만들어 주네요.

 

 

마침.

위에서 보면 값을 할당하고 그러잖아요? 네 Array 는 변경가능합니다. 변경 불가능 한것도 있다고 하네요. 

스칼라 언어는 변수의 값을 변경 할 수 있냐? 없냐를 매우 중요하게 생각하는거 같습니다.

'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (1 ) - 소개  (0) 2016.06.13

개인적으로 느끼는 스칼라라는 언어는 , 창시자가 너무 생각이 많구나~ 욕심도 많고 그런 느낌을 받습니다. "내가 쓰기 편하도록 엄청나게 많이 신경썼으니, 니들도 이걸 잘 쓰려면 내가 엄청나게 많이 신경쓴 그 부분들을 너희들도 일단 엄청나게 신경써서  공부해~~ 그 후엔  편해질꺼야" 이건데요.  그냥 안쓸래요. 라고 말해드리고 싶기도 합니다. ㅎㅎ 

또한 이 언어는 하이브리드입니다. 함수형과 객체지향 양쪽을 지원합니다. 함수형을 추구하되 객체지향도 쓸 수 있다 정도입니다만, 덕분에 굉장히 강력해 질 수도 혹은 복잡할 수 있습니다. 딱 맞는 예는 아니지만 마치 하이브리드 객체지향인 C++ 이  더 순수한  객체지향언어인 자바보다 복잡하듯이 말이죠. C++ 개발자중 객체지향에 대해서 잘 모르는 개발자들이 많듯이 스칼라 개발자 중에서도 함수형개발이 먼지 모르고 그냥 객체지향식으로 사용할 가능성도 매우 큽니다. 

따라서 먼저 클로저나 하스켈을 공부하고 스칼라를 하면 어떨까 생각 해봅니다. 저는 스칼라를 먼저 했는데 , 스칼라만 사용하는게 아니라 다양한 비함수형 언어와 동시에 개발을 진행 중이다보니 순수함수형의 DNA 가 뿌리내리기 정말 힘들더군요. 

아무튼 반응이 좋으면 100개는 채웠으면 하는 바람입니다.

재미로 보는 프로그래밍 언어들 특징

배우기도 쉽고 사용하기도 쉽다 : 파이썬,Golang
배우기는 쉬운데 사용하기는 어렵다 : C 
배우기는 어렵고 사용하기는 보통 : Scala,C++
배우기도 어렵고 사용하기도 어렵다 : 글쎄..
배우기 보통 사용하기 쉬운편 : 자바,스위프트

배우는것도 아리송하고 사용하기도 아리송하다 : Javascript 
참고 -> 심심풀이 자바스크립트 퀴즈 http://hamait.tistory.com/465


이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한Programming in Scala (2판)   
을 참고로 하여  공부하면서 정리할 예정입니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다.

 


 

 

오늘은 제가 선택한 스칼라가 자바와 다른점 4가지 포인트를 소개해보도록 하겠습니다. 

 

0.  함수

함수는 너무 중요해서 0 번입니다.

스칼라에서는 함수가 시작이자 끝입니다.
간단히 예를 들어보죠. 상상을 할 시간입니다. 객체지향을 잊어야합니다. 
 
수학에서 y = x + 1  이라는 함수가 있습니다.
개발자인 우리는 왼쪽  y 는 리턴 값 이라고 상상 할 수 있으며 오른쪽 x + 1 은 함수내용이라고 
생각 할 수 있습니다.
 
다시 수학으로 돌아 와서 저 수식에서  y 값은 x 가 무엇이냐에 따라서 항상 고정입니다.  
이런걸 굉장히 순수하다고 하는데요 왜냐?
 
x 에 2 를 넣었다고 생각해보자구요.
y = x + 1  이라고 표현 할 수 도 있지만 
y = 3 이라고 표현 할 수 도 있습니다. 
 
즉 함수가 하는 행위의 내용과 함수가 실행되어 나온 결과 값이 일치합니다.
매개변수로 2 가 입력 됬다고 할때  x+1 = 3 와 동일하다는 얘기죠.
즉  a = function (b) 
이런게 있을때  함수가 수학처럼 순수하다면 
a = 3 
이렇게 바꿀 수가 있다는 얘기입니다. 함수 자체와 결과가 일치합니다.
이러한 순수한 함수에 대해 공부하는게 스칼라에서 시작이자 끝입니다.
그럼 우리가 상식적으로 알고 있는 함수는 저게 일치 하지 않는데 왜 그럴까요?
int function ( int x ) {
return x + 1
}
 
이런 함수라면 순수합니다만..
int function ( int x ) {
  print(x)
  fileSave(x)
  collection.add(x)  
  return x + 1
}
 
위에 초록색처럼 내부에 이상한 짓거리를 하는 놈이 있다면 
과연  a = function (2) 를  a= 3이라고 말 할 수 있을까요?
해당 숫자를 넣으면 항상 같은 일이 벌어질거라는 확신을 가질 수 없게 됩니다.
이게 순수하지 않은 함수라는것이고 부수효과라고 말해지는것입니다.
스칼라는 순수한 함수를 지향하는 함수형 언어입니다.  (순수 함수형은 아닙니다.

C++ 이 C 개발자와 객체지향을 둘다 잡기 위한 약간의 혼종이라면 스칼라도 기존 자바/객체지향 세력을 포용하기 위한 느슨한 함수형 언어입니다. 하지만 이왕 스칼라를 사용한다면 함수형으로 마인드를 바꿔야합니다. 언어가 강제하지 않아서 헷갈릴지언정~) 

 

1.  객체 

1

자 이건 무엇인가요? 

숫자 1입니다.  땡~~~~!!  

이것은 객체 1 입니다.   

스칼라에서는 모든것이 객체입니다. 

1 + 3 

숫자를 더 하는거다?

아닙니다.  (뭐 아니라고 할 것 까진 없지만 객체라고 사고 전환이 필요해서리..) 

아래와 같습니다.

1.+(3)

저게 왜 같냐? 라고 묻는다면 

스칼라에서는 매개변수가 하나 일 때  괄호랑 .  를 생략할 수 있습니다.

1.+(3) 여기서  .  랑 괄호를 빼면  

그렇습니다.  1+3 이 됩니다. ㅇㅋ!! 

모든게 객체다 라고 사고전환을 하는게 매우 중요합니다.

다른 예를 하나 들면 

for ( i <- 0 to 2) {
  print (i) 
}

0 과 1 이 출력될텐데요. 

위에서  0 to 2  또한 

0.to(2) 를  . 과 () 를 생략해서 표현한 것입니다. 

즉 0 객체에 to 메서드를 이용하여  범위를 생성합니다. 

 

2. 타입-변수 vs  변수-타입 

자바는 int a; 라고 하지만 스칼라는 val a : Int 라고 합니다. 

이렇게 바꾼 이유는 "타입 추론" 을 용이하게 하기 위함이라고 하는데요 타입추론을 사용하면

변수 타입이나 메소드의 번환 타입을 생략 할 수 있게 됩니다.

즉 타입을 쓰지 않아도 언어내부에서 알아서 타입을 챙겨준다. 입니다. 

예를들어 

val a : Int = 1  해도 되지만

val a = 1 이렇게 생략해도 됩니다.   

val  o = new Test  하면 o 를 알아서 Test 객체로 추론한다는것이죠.

3.  val / var 

변경 불가능한 변수 / 변경 가능한 변수로 나누는 것입니다.  
갑자기  const 도 생각나네요. C++ 에서 const 는 변수를 변경 불가능하게 만드는 키워드입니다.


int const* p;   
const* int p;
int* const p;
const int* p;
const int const* p;
const int* const p;

const int ** p;
int * const *p;
int ** const p;

void const f(){};
void f() const{};
void f(const objectA i){}
void f(const 
objectA &i) {}

const int f(const char* const str) const {}
const int& f() const{}

이렇게 다양하게 표현가능합니다.
포인터가 가르키는것을 변경 불가능하게하라
포인터가 가르키는 것의 내용을 변경 불가능하게 하라
함수내부에서 XXX 을 변경 불가능하게하라. 
리턴되는 값이 변경 불가능하게 하라.

등등 입니다.  장난하나 -.-;; (대부분의 C++프로젝트에서 제대로 사용하지 않습니다. 역시 디폴트 불변이 필요합니다)

자 다시 스칼라로 돌아와서  val 과 var  에 대해서 알아 보겠습니다.

간단히 

val 은 변경 못한다는 뜻이구요.
var 은 변경해도 된다는 뜻입니다.

C,C++,Java,C# 은 var 가 일반적인 함수선언이며, 얼랭,하스켈,오캐멀등은 val 이 일반적인 변수입니다. 
하지만 스칼라는 var, val 은 그냥 사용자가 선택할 몫으로 두었습니다. 양쪽 다 유용하고 어떤 하나가 
특별히 악당이 아니라는 거죠.(자유를 주는 대신 헷갈림을 얻었습니다) 물론 스칼라도 val 을 권장하긴 합니다. 

소스를 보시죠.

val a = 10  a = 11  //  에러 !!!! 변경하지마~~  
var b = 10  b = 11  // 좋습니다 !! var 는 변경을 허용합니다.

근데 여기서 잘 생각해보셔야할게 하나 있는데요.  
저기서 변환을 못시킨다는건 참조가 바뀌면 안된다는거지 
참조하고 있는 대상안의 값은 바뀌어도 무방합니다. 

예를들어 

 class Test {    
  var a = 1              
  //  스칼라는 함수선언시 def 를 사용합니다. 함수도 객체라 = 대입받습니다.       
  def set(n:Int)={a = n}    
  def print()={println(a)}  
 }     
 
 val test = new Test()  // 변경 불가능한 val 로 선언합니다.    
 test.print()  // test 객체를 출력하면 1 이 나옵니다.   
 test.set(10) // 10 으로 "변경" 합니다. ??  엥 변경 ???    
 test.print() // 10 으로 변경 되었습니다.  별 이상 없습니다.            
 test2 = new classTest() // 새로운 객체를 만들고   
 test = test2  // 새로운 객체를 대입받으려고 하면 ~ 네 변경 불가능합니다!!

val 로 선언했어도 set 함수를 통해 내부의 값을 바꾸는데 아무 이상없습니다.

스칼라에서는 var 을 어떤식으로 없애냐면 

def printArgs(args: Array[String]): Unit = {
  // String 형 배열을 인자로 받고, 리턴되는 값은 없다.     
  var i = 0   
  while (i < args.length){     
    println(args(i))     
    i += 1      
  } 
}
def printArgs(args: Array[String]): Unit = {
  for (arg <- args){  
    // args 인자에서 하나씩 추출되어 arg 에 담김      
    println(arg)     
  } 
}
def printArgs(args: Array[String]): Unit = {
  // args 에서 순회하는 메소드를 그냥 호출   
  args.foreach(println)  
}

점점 간단해집니다. 

하지만 서두에도 말했다시피 우린 그냥 평범한 if, for, while 만 가지고 그동안 코딩 잘 해왔는데 
굳이 (순수함수를 고민하고 재귀를 기본으로 사고하기도 하는) 다른 방식의 표현을 배우고 외워야합니다. 복잡도가 오히려 늘어난 것 처럼
보이기도 합니다. 저도 아직 잘 모르겠습니다. 

오늘은 요기까지 하구요~~

내일은 새로운 언어를 배우면 가장 먼저 익숙해져야하는  기본 콜렉션 몇가지를 살펴보도록 하겠습니다.

Array / List / Tuple / Set / Map 같은거 말이죠. 

 

 

'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13

+ Recent posts