3. 스칼라 두번째 걸음

6449 단어 scala스칼라scala

3.1 배열에 타입 파라미터를 지정해보자

스칼라에서는 new 키워드를 사용해 객체를 인스턴스화 할 수 있다.
(즉, 클래스의 인스턴스를 만들 수 있다.)
스칼라에서 객체를 인스턴스화 할 떄, 값과 타입을 파라미터로 넘길 수 있다.

val big = new java.math.BigInteger("12345")

리스트 3.1 배열을 타입으로 파라미터화하기

val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"

for (i <- 0 to 2)
  print(greetStrings(i))

좀 더 명시적으로 타입을 선언하고 싶다면...

val greetStrings: Array[String] = new Array[String](3)

greetStrings은 Array[String]이라는 타입의 값
이 배열을 3이라는 값으로 파라미터화함으로써 길이를 초기화
타입과 값을 가지고 인스턴스화할 때는 먼저 타입을 각 괄호 사이에 지정하고, 값을 괄호 사이에 지정한다.

val 로 선언한 변수는 재할당 할 수 없다.
하지만, 변수가 나타내는 객체는 잠재적으로 변경이 가능하다.

greetStrings에는 다른 변수를 넣을 수는 없지만, greetStrings(0), greetStrings(1), greetStrings(2) 와 같은 Array[String]의 원소는 언제나 변경할 수 있다 즉, 배열자체는 mutable 하다.

스칼라에서 왜 괄호를 사용해 배열에 접근 할 수 있는가?

  • 어떤 종류의 객체이든 괄호 안에 인자를 넣어서 호출하면 apply 메소드를 호출하는 것과 같음. (ex, greetString(i) 는 greetString.apply(i)와 같다.)
  • 스칼라에서 배열의 원소 접근하는 것은 일반적인 메소드 호출과 같다.
  • 1 + 2 은 (1).+(2) 와 같다. 1이라는 Int 객체에 + 라는 이름의 메소드를 호출하고, Int 객체 2를 + 메소드에 전달한다.
  • 마찬가지로, 어떤 변수 뒤에 괄호로 둘러싼 인자들이 있는 표현식에 할당을 하면 괄호안에 있는 인자와 등호 오른쪽의 값을 모두 인자로 넣어서 update 메소드를 호출.
val greetStrings = new Array[String](3)
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")

for (i <- 0 to 2)
  print(greetStrings.applt(i))

3.2 리스트를 사용해보자

함수형 프로그래밍의 가장 큰 착안점 → 메소드에 부수 효과가 없어야 함.

  • 장점 1 ) 이를 통해 더 많이 신뢰할 수 있고 재사용할 수 있음.
  • 장점 2 ) 메소드에 들어가고 나오는 모든 것을 타입 검사기가 검사하기 때문에 논리적인 오류가 타입 오류라는 형태로 드러날 확률이 높아짐.
  • 이런 함수적인 철학을 객체의 세계에 적용하면, 객체를 변경 불가능하게 만든다.

지금까지 스칼라 배열은 모든 원소의 타입이 같은 객체로 이뤄진 변경가능한 시퀀스였다.
-> 같은 타입의 객체로 이루어진 변경 불가능한 시퀀스를 위해서는 List를 사용할 수 있다.

스칼라의 리스트인 scala.List는 변경 불가능하다는 점에서 자바의 java.util.List 타입과 다르다. (자바의 리스트는 원소를 바꿀 수 있었다.)

리스트를 만들고 초기화하기
val oneTwoThree = List(1, 2, 3)
  • List는 변경 불가능하기 때문에 자바 문자열과 약간 비슷하게 동작.
  • List의 내용을 변경하는 것 같아 보이는 메소드를 호출하면, 새로운 리스트를 만들어 반환.
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
println(oneTwo + " and " + threeFour + "were not mutated.")
println("Thus, " + oneTwoThreeFour + "is  new list.")

//결과
List(1, 2) and List(3, 4) were not mutated.
Thus, List(1, 2, 3, 4) is a new list.

'::' 콘즈(cons) 메소드는 두 리스트를 연결하여 새로운 List를 만든다.

val oneTwoThree = 1 :: 2 :: 3 :: Nil
println(oneTwoThree)

'Nil' 빈 리스트 Nil을 사용하여 새롭게 리스트를 초기화 할 수 있다.


3.3 튜플을 사용해보자

튜플(tuple)은 리스트와 마찬가지로 변경 불가능하지만, 튜플에는 각기 다른 타입의 원소를 넣을 수 있다.

val pair(99, ""Luftballons)
println(pair._1)
println(pair._2)
  • pair의 타입은 Tuple2 [Int, String] 이다.
  • 튜플의 원소 타입은 각기 다를 수 있기 때문에, _1, _2와 같은 타입으로 사용한다. 또한, 인덱스가 0이 아닌 1 부터 시작한다. 하스켈이나 ML처럼 정적인 타입을 사용하는 언어에서 전통적으로 튜플의 인덱스를 1부터 세어왔기 때문

3.4 집합과 맵을 써보자

스칼라의 목적은 프로그래머가 함수형스타일과 명령형스타일의 장점을 모두 취할 수 있도록 돕는 것, 따라서 스칼라 컬렉션 라이브러리에서 변경가능한 컬렉션과 변경 불가능한 컬렉션을 구분

변경 불가능한 집합을 만들고, 초기화하고 사용하기

  • 1행 - jetSet이라는 새로운 var 변수 정의 후 두 문자열을 포함하는 변경 불가능한 집합으로 초기화.
  • 2행 - + 메소드를 통해 원소를 추가후 새로운 집합을 반환. (실제로는 jetSet = jetSet + "Lear" 로 동작)
  • 3행 - 출력
  • 4행 - 해당 집합에 Cessna 문자열 있는지 여부 출력.
변경 가능한 집합을 만들고, 초기화하고 사용하기

  • 1행 - 변경 가능한 집합을 임포트.
  • 2행 - val movieSet 을 두 문자열이 들어간 변경 가능한 집합으로 초기화.
  • 3행 - += 메소드 통해서 새로운 원소 추가.
  • 4행 - 출력.

Map trait

변경 가능한 Map을 만들고, 초기화하고 사용하기

  • 1행 - 변경 불가능한 val romanNumeral 맵 생성 및 키, 값 할당.
  • 2행 - 4라는 키에 대응하는 값 출력.
변경 불가능한 Map을 만들고, 초기화하고 사용하기

  • 1행 - 변경 가능한 맵을 임포트.
  • 2행 - val treasureMap 정수 키와 문자열 값을 담을 수 있는 변경 가능한 빈 Map으로 초기화.
  • 3~5행 - -> , += 메소드를 통해 키/값 쌍을 할당.
  • 6행 - 2라는 키에 대응하는 값 출력.

3.5 함수형 스타일을 인식하는 법을 배워보자

// 명령형 스타일로 함수 선언
// var가 있기 때문에 명령형 스타일
def printArgs(args: Array[String]): Unit = {
    var i = 0;
    while (i < args.length) {
        println(args(i))
        i += 1
    }
}
 
// 함수형 스타일로 함수 선언
def printArgs(args: Array[String)): Unit = {
    for (arg <- args)
        println(arg)
}
 
// 또는
def printArgs(args: Array[String]: Unit) = {
    args.foreach(println)
}

3.6 파일의 내용을 줄단위로 읽자

어떤 파일의 모든 줄의 문자 개수를 줄을 잘 맞춰 출력하기
import scala.io.Source // Source 메소드 임포트

// 문자열 길이 계산한다.
def widthOfLength(s: String) = s.length.toString.length             
 
if (args.length > 0) {
    val lines = Source.fromFile(args(0)).getLines().toList          // toList를 호출해 리스트로 만든다.
    val longestLine = lines.reduceLeft(                             // 최대값 찾는다.
        (a, b) => if (a.length > b.length) a else b  
    )
    val maxWidth = widthOfLength(longestLine)                       // 최대 너비 계산
    for (line <- lines) {                                            // 최대 너비에서 현재 라인의 너비를 제외하고 그 만큼의 공백을 삽입
        val numSpaces = maxWidth - widthOfLength(line)
        val padding = " " * numSpaces
        println(padding + line.length + " | " + line)
    }
}
else
    Console.err.println("Please enter fileName")

좋은 웹페이지 즐겨찾기