본문 바로가기

옥탑방주인/Scala

Chapter 2. Expressions, Types, and Values



 이 챕터에서는, 스칼라 프로그램의 블록들(expression, types, values)을 기본적으로 빌딩하는것을 알아 볼 것이다.  이러한 개념들을 이해하는것은 어떻게 스칼라 프로그램이 작동하는지에 대한 mental model을 빌드하는데 필수적이다.


2.1.    Your First Program


스칼라 콘솔 또는 워크시트(IDE 말하는듯)에서 "Hello world!"를 입력하고 리턴을 누르거나(콘솔에서) 워크시트를 저장해라. 아래에서 이런 비슷한 반응을 볼 수 있다.

"Hello world!"
// res: String = Hello world!

이 프로그램에 관해 할 얘기가 많다. 이것은 single expression, particular a literal expression 또는 literal for short로 구성되어 있다.


스칼라는 프로그램을 실행하거나 평가한다. 스칼라 콘솔 또는 worksheet에서 프로그램을 평가할때, 두가지의 정보를 말할수있다: 프로그램의 타입, 그리고 평가하는 값어치. "Hello world!"에서 타입은 String이고 값은 "Hello world!"이다.


출력 값 "Hello world!"는 생성된 프로그램과 비슷하게 보이지만, 두개의 차이점(?)이 있다. literal expression은 우리가 입력한 프로그램 텍스트이고, 콘솔이 출력하는것은 프로그램이 평가한 결과이다.(리터럴은 말 그대로 평가되기때문에 붙여진것이다.)

약간 더 복잡한 프로그램을 보자

"Hello world!".toUpperCase
// res: String = HELLO WORLD!

이 프로그램은 첫번째 예제에 method call을 추가하여 확장한 프로그램이다. scala에서 evaluation은 오른쪽에서 왼쪽으로 진행된다(코드 진행방향을 말하는듯).첫번째 리터럴 "Hello world!"는 첫 예제와 같이 평가되었다. 그 후 toUpperCase 메소드가 결과에 호출되었다. 이 메소드는 문자열의 값을 대문자로 변환시키고 새 문자열로 리턴시킨다. 이것이 콘솔에 의해 최종 출력된 값이다.


다시한번 말하지만 이 프로그램의 타입은 String이지만, 이 경우에는 "HELLO WORLD!"로 평가된다.


2.1.1 Compile-time and Run-time


스칼라 프로그램에는 두 단계가 있다: 첫번째는 컴파일이 되어있고, 컴파일이 완벽하게 되었다면 그 후 실행하거나 평가된다. 첫번째 단계는 컴파일타임으로 다음단계는 run-time으로 언급되어있다.

스칼라콘솔에서 컴파일 하자마자 프로그램은 평가되고(evaluated) 이것은 오직 한 스테이지에 해당한다(??두 단계가 있는데 이것은 한 단계로만 표현되있다고 하는듯). 이것은 컴파일과 런타임이 실제로는 별개임을 이해하는데 매우 중요하다. 타입(types)과 값(values)의 다른점을 이해할 수 있게 해준다. 

컴파일은 프로그램이 의미가 있는지 확인하는 과정이다. 프로그램에 의미가 있게 하는방법에는 두가지가 있다.


1. 문법이 정확해야 하고, 프로그램이 의미하는 부분이 언어의 문법에 따라 배열되어야 한다(문법에 알맞게 선언해야 한다는듯). 예를들면, 영어문장에서 옳바르지 않은 예로 "on cat mat sat the"라는 문장이 있다. 스칼라프로그램에서 문법적으로 옳바르지 않으면 아래와 같이 나타난다.

toUpperCase."Hello world!"
// error: identifier expected but string literal found.
//        toUpperCase."Hello world!"
//                           ^
2. 타입검사를 해야한다,의미있는 프로그램이 무엇인지에 대한 일정한 제약에 복종해야 함을 의미한다. 영어 문법적으로는 옳았지만 말이안되서 실패한 경우는 이렇다 "the mat sat on the cat". 숫자를 upper case로 변환하려고 하는 것을 실패하는 type check의 간단한 프로그램은 이렇다.

2.toUpperCase
// error: value toUpperCase is not a member of Int
//                    2.toUpperCase
//                       ^


upper와 lowercase의 개념은 숫자에 의미가 없고, 타입 시스템(type system)은 이 에러를 잡아낸다.

만약 컴파일타임에 프로그램이 체크를 패스한다면 실행이 될 것이다. 스칼라에서 정수로(Integer)0을 나누면 런타임 에러가 발생한다.

2 / 0
//  java.lang.ArithmeticException:  /  by zero

정수형인 Int는 나누기를 허용하여 프로그램 유형을 확인한다. 런타임 프로그램은 실패한다, 왜냐하면 나누는 결과를 나타낼 수 있는 Int가 없기 때문이다(자바에선 0을 정수로 나누는것을 허용하지 않음).



2.1.2    Expressions, Types, and Values


표현식(expressions), 타입(types), 값(values)는 정확히 뭘까?

표현식은 파일 또는 워크시트 또는 콘솔안에 타이핑하는 프로그램의 텍스트의 일부이다. 이런 텍스트들은 스칼라 프로그램의 메인 컴포넌트이다. 곧 다른 컴포넌트들을 보게될것인데, 이 코스에서는 definitionsstatements 라고 정해져있다. 표현식은 컴파일타임에 존재한다.


표현식을 정의하는 특징은 값(value)을 평가하는 것이다. 값은 런타임에 존재하며, 컴퓨터 메모리에 저장되어있는 정보이다.  예를들어, 표현식 2는 컴퓨터 메모리의 특정부분에서 특정 비트들의 연속적인것으로 평가된다(메모리에서 계산되어 나온다는것인듯 OS시간 같네요 ㅎㅎ). 


위에서 말한것들은 프로그램을 다루거나 전달할 수 있는것이다. 예를들어, 2개의 숫자 중 최소값을 계산하는 프로그램은 이럴것이다.

2.min(3)
// res: Int = 2

값2와 3 이 있고, 그것들을 2로 평가되는 더 큰 프로그램으로 결합한다. 스칼라에서는 모든 값들은 객체(object)이다. 이러한것들의 설명은 조만간 볼 수 있다.


이제 types을 공부하러 가보자. 타입은 객체를 다룰수있는것을 제한하는 프로그램의 제약사항(restriction)이다. 위에서 우리는 2가지 타입에 대해 이미 공부했었다. String 과 Int, 그리고 타입에 따라 다른 작업을 실행하는것을 보았다. 여기서 가장 중요한 점은 타입(type)에는 expression 이 있지만 값(value)에는 없다. 우리는 컴퓨터 메모리의 임의적인 부분들을 세세하게 알 수 없고, 만들어진 프로그램을 이해하지 못하면 interpret 할 수 없다. 예를들어, 스칼라에서 Int와 Float형은 둘다 32비트 메모리에서 표현된다. 주어진 32비트에서는 Int 또는 Float형으로 해석되는것에 대한 태그 또는 다른 표시(indications)가 없기 때문이다. 


스칼라 콘솔에 런타임 오류를 발생시키는 표현식의 타입을 알려달라고 요청하면 컴파일 타임에 타입이 존재 함을 알 수 있다. 

:type 2 / 0
// Int

2 / 0
// java.lang.ArithmeticException: / by zero

2 / 0 을 평가하는게 실패했을지라도 2 / 0 의 타입이 Int형이라는 표현식을 확인할 수 있다.


컴파일 타임에 존재하는 타입은 값에 대한 일관된 해석을 제공하는 프로그램 작성을 제한한다. 프로그램 타입이 확인되면, 스칼라는 지속적으로 사용되는 모든 값들을 보증하기 때문에 값들을 표현하는 타입의 정보를 저장할 필요가 없다. 이런 타입정보를 지우는 과정을 type erasure 라고 불린다(이것이 전적으로 옳지는 않다고 나와있다. primitive type은 타입을 저장하지 않지만 Object 타입은 타입정보를 저장한다고 나와있다).


타입은 반드시 타입에 일치하는 값에 대한 모든 가능한 정보를 반드시 포함하지는 않는다. 타입검사는 프로그램을 실행하는것과 같다. 이미 이전에 0(zero)를 Int형으로 나누는것을 방지하지 못하는 run-time에러를 발생시킨 타입 시스템을 보았다. 스칼라 코드를 설계하는 핵심 부분은 타입시스템을 사용해서 제외하려는 오류 케이스(error case)들을 결정하는 것이다.  타입 시스템에서 많은 유용한 제약을 표현할 수 있어 프로그램의 신뢰성을 향상시킬 수 있음을 알 수 있다. 우리의 프로그램에서 충분히 중요하다고 결정한 경우 타입 시스템을 사용하여 오류 가능성을 표현하는 나누기 연산자를 구현할 수 있다. 타입시스템을 잘 사용하는것은 이 책의 중요한 내용중의 하나일수도 있다.



2.1.3    Take Home Points


우리가 스칼라를 사용한다면 스칼라 프로그램의 mental model을 반드시 빌드해야 한다. 이 mental model의 세가지 중요한 컴포넌트는 expressions, types, values 이다.


표현식은 value를 평가하는 프로그램의 중요한 부분중 하나이다. 이것은 스칼라 프로그램의 중요 부분중 하나이기도 하다.


표현식은 프로그램에서 다수의 제약(restrictions)을 표현하는 타입(types)을 갖고있다. 프로그램의 타입은 compile-time 하는동안 체크된다. 표현식에 일관성이 없다면 컴파일은 실패되고 프로그램을 실행하거나 evaluate 할 수 없다(문법이 틀리면 실행이 안된다는 것 같다). 값(values)은 컴퓨터의 메모리에 존재하고, 실행중인 프로그램을 조작한다. 스칼라에서 모든 값은 object 이고, 추후에 자세히 설명하겠다.


2.1.4    Exercises


2.1.4.1    Type and Value


스칼라 콘솔 또는 IDE를 사용하면, 다음의 표현식의 값과 타입을 알아낼 수 있다:

1 + 2


See the solution

"3".toInt


See the solution

"foo".toInt


2.2    Interacting with Objects


이전 섹션에서 스칼라 프로그램의 기초적인 컴포넌트들을 보았었다: expressions, values, types.

모든 값은 객체로 되있다는것을 배웠었다(We learned that all values are objects). 이번 섹션에서는 객체(object)에 관해 더 알아볼 것이고, 어떻게 객체와 상호작용하는지에 대해 알아볼 것이다.


2.2.1    Objects


객체는 데이터의 그룹과 데이터의 작업이다. 예를들어 2는 객체이다. 데이터는 정수형(Integer) 2이고, 데이터를 동작시키는것은 +, -같은 작업과 비슷하다.


데이터와 객체의 동작에 관한 몇개의 전문용어가 존재한다. operation은 아시다시피 methods라고 부르고 data는 fields에서 저장된다. 


2.2.2    Method Calls


methods라고 불리는 객체와 상호작용해보자. 이미 이전에 method를 호출하는 몇몇의 예제를 보았었다. 예를들어, toUpperCase 메소드를 호출해서 String의 버전을 uppercase로 얻는것을 봤었다.

"hello".uoUppercase
// res: String = HELLO

일부 메소드는 메소드가 작동하는 방식을 제어하는 매개 변수(parameters) 또는 인자(arguments)를 허용한다. 예를들어 take 메소드는 String으로부터 문자열을 가져온다. 매개변수를 통해서 우리가 원하는 문자열의 수를 지정할 수 있다. 

"abcdef".take(3)
// res: String = abc

"abcdef".take(2)
// res: String = ab


 

 


  Method Call Syntax


 메소드를 부르기위한 문법은 


 anExpression.methodName(paraml, ...)


 또는


 anExpression.methodName


 Where

    • anExpression은(객체를 평가하는) 모든 표현식이다.
    • methodName은 메소드의 이름이다.
    • 선택적인 param1, ...은 메소드에 대한 매개 변수를 평가하는 하나 이상의 표현식이다.


메소드 호출은 표현식이므로 객체로 평가된다. 이 의미는 메소드 콜은 더 복잡한 프로그램을 만드는것에 함께 사용할 수 있다.

"hello".toUpperCase.toLowerCase
// res: String = hello

메소드 호출의 다양한 표현식은 어떻게 평가될까? 메소드 매개 변수들은 메소드가 호출되기 전에 왼쪽 에서 오른쪽으로 평가된다. 밑에 표현식을 보자

"Hello world!".take(2 + 3)

표현식 "Hello world"는 첫번째로 평가되고, 그 후 2+3(2를 먼저 평가한다음 3이 평가된다),그리고 마지막으로 "Hello world!".take(5)순으로 평가된다.



2.2.3    Operators


스칼라에서 모든 값은 객체이기 때문에 Int나 Boolean과 같은 기본타입의 메소드를 호출할 수 있다.

자바와 반대되는 점은 자바에서는 int와 boolean은 객체로 선언할 수 없지만 scala에선 가능하다.

123.toShort // this is how we define a 'Short" in Scala
// res: Short = 123

123.toByte // and this is how we define a 'Byte'
// res: Byte = 123

그러나 Int가 객체이면, 수학 연산자인 +와 -같은것은 무엇일까? 이것도 마찬가지로 메소드일까?

정답은 yes다. Scala 메소드는 기호 이름과 문자와 숫자같은것도 포함된다.


43 - 3 + 2
// res: Int = 42

43.-(3).+(2)
// res: Int = 42

(스칼라 2.10과 이전 버전에서는 (43).-(3).+(2)이 43이 되는걸 방지하기 위해 Double형처럼 변환시켰다.)


 

 


  중위 동작 표현식??(Infix Operator Notation)


  모든 스칼라에서 a.b(c)로 쓰여진 표현식은 a b c. 와 똑같다.


  a b c d e가 a.b(c).d(e)와 동일하지만 a.b(c, d, e)랑은 다르다.



 ====

 여기선 prefix, infix, postfix 에 대한 개념이 필요한 부분인 것 같다.


우리는 하나의 매개 변수를 취하는 모든 메소드에 기호식 또는 문자나 숫자로 되어있는 여부에 관계없이 중위 연산자 표기법을 사용할 수 있습니다.

"the quick brown fox" split " "
// res: Array[String] = Array(the, quick, brown, fox)

중위 표기법은 몇가지 함축된 구문중의 하나로 세세하게 표현되있는 메소드 호출 대신 간단한 연산자 식을 작성할 수 있다. 마찬가지로 prefix, postfix, right-associative, assignment-style operator 표기법도 있지만 infix notation보단 덜 사용된다. 중위 연산자와 연관시켜야 할 우선순위 규칙(precedence rules)은 무엇인가?? 스칼라는 식별자로부터 파생된 여러개의 우선순위 규칙을 사용하고, 수학과 로직으로부터 직관적으로 이해를 할 수 있는 메소드이름을 사용한다.

2 * 3 + 4 * 5
// res: Int = 26

(2 * 3) + (4 * 5)
// res: Int = 26

2 * (3 + 4) * 5
// res: Int = 70



2.2.4    Take home points

모든 스칼라의 값은 객체이다. object와 상호착용하려면 메소드로 호출해야 한다. 만약 자바 사용자였다면 알아둬라, Int 또는 다른 기본 값에 대한 메소드를 호출할 수 있다.

메소드 호출을 위한 문법
anExpression.methodName(parameter, ...)

또는
anExpression methodName parameter

스칼라에선 연산자 수가 매우 적다- 거의 모든것이 메소드 호출이다. 중의 연산 표기법(infix operator notation)같은 구문 규칙(syntactic convention)을 사용해서 간단하고 읽기쉬운 코드를 유지시키지만, 표준 메소드 표기법을 언제든 쓸 수 있다.

앞으로 보게될 것이지만, 스칼라는 자바보다 더 짧은 코드를 사용해서 프로그래밍할 수 있고, 표현식을 사용해서 프로그래밍하는것에 중점을 두고있다. 마찬가지로 값(values)와 타입(types)을 사용해서 매우 직관적인 코드를 볼 수 있다.


2.2.5    Exercises

2.2.5.1    Operator Style

Rewrite in operator-style

"foo".take(1)

Solution : "foo" take 1



Rewrite in method call style

1 + 2 + 3

solution : 1.+(2).+(3)



2.2.5.2    치환(Substitution)


밑에 두가지 표현식에 무엇이 다르고 무엇이 비슷한점일까?

1 + 2 + 3

6

solution;


표현식은 같은 결과 타입과 리턴값을 갖고있다. 그러나, 결과에 다른 방식으로 도달한다. 첫번째는 덧셈을 통해 결과를 계산하지만 나중에는 단순히 상수이다. 


어느 표현식에도 부작용이 없으므로, 사용자의 관점에서 판단해서 사용하면 된다. 1 + 2 + 3을 썼던 6을 썼던 마찬가지다. 이것은 단순치 치환으로 알려져있고 학교에서 간단한 수학공식의 원리로 배웠을 것이다.


프로그래머로서, 우리는 코드가 어떻게 동작하는지에 대한 정신적인 모델을 개발해야 한다. 부작용이 없는 한, 치환모델은 항상 동작한다. 각 표현식의 컴포넌트에 타입과 값을 단다면, 표현식 전체의 타입과 값을 알 수 있다. 함수지향 프로그래밍(functional programming)에선 우리의 프로그램을 더 이해하기 쉽게 만들기 위해서 부작용을 방지해야 한다.



2.3    Literal Object


위에서 스칼라의 기본 타입 몇개를 다뤄보았다. 이번섹션에서는 모든 스칼라의 리터럴 표현식(literal expression)을 상세하게 다뤄볼 것이다. 리터럴 표현식은 선언한 그대로 값이 고정되어 나타난다. 

42
// res: Int = 42

REPL에서 반응한 리터럴42는 Int 42로 평가되었다.


리터럴을 평가할 값과 혼동하면 안된다! 리터럴표현식은 프로그램이 실행되기전에 프로그램 텍스트에서 표현되고 값은 프로그램이 실행 된 후 컴퓨터의 메모리에서 표현된다. 


자바 프로그래밍에 경험이 있었다면, 스칼라에서 리터럴은 당신에게 친숙할 것이다.



2.3.1    Numbers


number는 자바에서 사용할 수 있는 동일한 타입을 공유한다: Int형은 32-bit 정수이고, Double형은 64-bit floating point이고, Float는 32-bit floating point이고, Long은 64-bit 정수형이다.

42
// res: Int = 42

42.0
// res: Double = 42.0

42.0f
// res: Float = 42.0

42L
// res: Long = 42

스칼라는 마찬가지로 16-bit Short형 정수와 8-bit Bytes가 있지만 이러한 형태로 선언할 수 있는 문법이 없다. 대신에, toShort 와 toByte같은 메소드 호출을 사용해서 Short형이나 Byte형을 만들 수 있다.



2.3.2    Booleans


Boolean은 Java와 완전히 똑같다: true 또는 false

true
// res: Boolean = true

false
// res: Boolean = false


2.3.3    Characters


Char은 ''안에 들어있는 단일문자열로 쓰여진 16-bit 유니코드 값이다.

'a'
// res: Char = a


 

  


 Scala vs Java's Type 계층


 스칼라에서는 첫단어가 대문자로 선언되었다. Int, Double, Float, Long, Short, Byte, Boolean그리고 Char. 하지만 자바에서는 int,double,float,long,short,byte,boolean,char로 되어있다.


 스칼라에서 이러한 모든 유형은 메소드 및 필드가 있는 객체와 같은 역할을 한다. 그러나, 당신의 코드가 이미 컴파일되었다면, 스칼라 Int는 Java int와 정확히 같을것이다. 따라서 두 언어의 상호 운영성이 한결 더 수월해진다.



2.3.4    String


String은 Java의 String과 완벽히 똑같다.

"this is a string"
// res: java.alang.String = this is a string

"the/nusual/tescape characters apply"
// res: java.lang.String =
// the
//        usual escape characters apply



2.3.5    Null


Null은 자바와 같지만, 보통 거의 사용되지 않는다. 스칼라 null은 마찬가지로 type: Null이다.

null
// res: Null = null


 

 


  스칼라에서 Null을 사용하는법

  

  











'옥탑방주인 > Scala' 카테고리의 다른 글

Scala에서 변수의 정의  (0) 2018.06.28
Scala에서의 파라메타(parameter)와 인자(argument) 구분법  (0) 2018.06.27
Chapter 1. Getting Started  (0) 2017.11.21
매개변수 매개변수 목록  (0) 2017.10.12
튜플(Tuple)  (0) 2017.10.10