클래스와 객체 

   

 

 클래스,필드,메소드
  
 객체 생성
  클래스는 객체의 청사진이다.
  class ChecksumAccumulator {
  } 

  이런 클래스 가 있을때  

  new ChecksumAccumulator  // 이렇게 해주면 객체가 만들어 진다.
  클래스 안에는 필드와 메소드를 넣을 수 있다. 이 둘을 합쳐 멤버라고 한다.

  

  필드는 var 이나 val 로 정의하며

  메소드는 def 로 정의 한다.

  class ChecksumAccumulator {
       var sum = 0 

  } 

 위의 클래스를 가지고 객체를 2개 만들면 

 val a = new ChecksumAccumulator 

 val b = new ChecksumAccumulator

 

 해당 객체안의 sum 필드는 다른 메모리를 참조 할 것이고 0 을 바라 볼 것이다.

 sum 이 var 이기 때문에, 나중에 다른 Int 값을 재할당 할 수 있다.

 a.sum = 3 // 요렇게 

 객체가 val 인데도 불구하고 내부의 값을 바꿀 수 있다는 점을 체크하라. 

 다만 처음 생성한 그 객체를 바라보고 있다는 확신은 가질 수 있다. 

 

 필드 접근성  

* 필드를 비공개로 만들어서 직접 접근하지 못하게 하라. private 을 사용한다. 

    - 아무것도 안붙히면 전체 공개. (자바와 다르다) 

 

 메소드 특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.

  def add(b : Byte) : Unit = {

b = 1  // 파라미터가 val 이라 불가능 !

sum += b

   }

 * return 을 명시적으로 사용하지 말라. 

   스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.

 * 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.

   def add(b:Byte) : Unit = sum += b

 * 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다. 

 * Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데 
   프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다. 

   즉 def add(b: Byte) { sum += b}  처럼 사용한다는 뜻이데, 여기서 함정이 있는게 

   def add2 () { " test string " }  이건 Unit 를 리턴하며 
   def add3 () = { " test string " }  이건 String 을 리턴한다는 것이다. 

   즉 프로시저 처럼 작성하면 리턴이 무조건 Unit 이 된다는점~ 

 

 세미콜론 추론 

  스칼라에서는 보통 문장 끝의 세미콜론(;) 을 생략 할 수 있다.

  다만 한 줄에 여러 문장이라면 사용해야한다. 

  가끔 헷갈리는것은

  x

  + y 

 스칼라는 위 문장을 x 와 +y로 파싱한다. 

 (x

 +y)  이렇게 하거나 

+ 를 줄의 끝에 넣으면 우리가 의도하는대로 된다. 

x + 

y  

 

싱글톤 객체

스칼라와 자바의 가장 큰 차이점 중 하나는 스칼라는 정적 멤버가 없다는 것이다.

대신 전용으로 사용할 수 있는 싱글톤 객체를 제공한다. 

class 대신 object 라는 키워드를 사용한다.

import scala.collection.mutable.Map 

object ChecksumAccumulator {

	private val cache = Map[String, Int) ()
	def calculate(s: String) : Int = 

		if (cache.contains(s))
			cache(s)
		else {

			val acc = new ChecksumAccumulator

		for (c <- s)
			acc.add(c.toByte)
		val cs = acc.checksum()
		cache += (s -> cs)    // s 키, cs 값 으로 연관관계를 만듬. 
		cs
}

}

이 싱글톤 객체는 이름이 저 위에 있는 class ChecksumAccumulator 와 같다.

이렇게 싱글톤 객체는 동일하게 만들 수 있는데 이걸 동반 객체라고 하며 

같은 파일안에 있어야한다. 

이런 클래스와 동반객체는 상대방의 비공개 멤버에 접근 할 수 있게 된다.

즉 자바의 정적멤버가 외부에 따로 만들어져 있다고 보면 된다.

ChecksumAccumulator.calculate("hello") 이렇게 호출 할 수 있다. 

* 팁:  코드에서 맵 경우 WeakHashMap 같은 걸 사용하면 메모리가 부족할때 가비지콜렉터가 캐시의 원소를 수집.

- 싱글턴 객체는 파라미터를 받을 수 없고 클래스는 받을 수 있다.

- 동반 클래스가 없는 싱글톤 클래스를 독립 객체라고 한다. 

  독립객체로는 유틸리티 메소드를 모아두는곳 이나 진입점으로 사용할 수 있다. 

 

스칼라 애플리케이션 

import ChecksumAccumulator.calculate

object Sumer {

	def main(args: Array[String]){

		for (arg <- args)

			println(arg : ": " + calculate(arg() )  // 메소드만 사용함!

	}

}

 * 스칼라는 항상 java.lang 과 scala 패키지의 멤버를 암시적으로 임포트한다.

또한 Predef 이라는 싱글톤 객체도 임포트하는데 여기엔 유용한 메소드가 많이 있다.

println 이라든지 assert 라든지..

 

 * 스칼라의 import 는 자바의 static import 로 생각 할 수 있다. 스칼라에서 다른 점은 싱글톤 뿐 아니라 어느 객체에서라도 멤버를 임포트 할 수 있다는 것이다.

* 자바는 공개  클래스 이름과 파일 이름이 같아야 하지만 스칼라에서는 상관없다. 

* scala.Application 이라는 트레이트를 통해 더 간단히 main 을 작성 할 수 있다.

import ChecksumAccumulator.calculate

object Sumer extends Application  {

	for (arg <- List("fall","winter"))

		println(arg : ": " + calculate(arg() )  // 메소드만 사용함!

}

 

클래스 예제

 

1) 간단한 클래스  

 

# 한줄 만으로 class 완성! 
class Person(var name: String, var age: Int) // 기본 생성자가 class 이름과 나란히~ 

# class 사용 
val al = new Person("Al", 42) 
al.name // "Al" 
al.age // 42

 

 

2) 자바와 다른 특이한 모습

 

// class 정의 
class Person(val firstName: String, val lastName: String) {
 println("the constructor begins") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다.
 val fullName = firstName + " " + lastName 
 val HOME = System.getProperty("user.home"); 
 def foo { println("foo") } 
 def printFullName { println(fullName) } 

 printFullName // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. 
 println("still in the constructor") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. } 

// 다음과 같이 나옵니다. 클래스 내부의 것들이 생성됨과 동시에 실행되네요. 
scala> val p = new Person("Alvin", "Alexander") 
the constructor begins 
Alvin Alexander 
still in the constructor 
p: Person = Person@68f507d2

 

3) 보조 생성자 

 this 를 사용하여  보조적 생성자를 만들수 있네요. (클래스 이름가지고 만들지 않음) 

 

class Pizza { // 기본 생성자에는 매개변수를 받는게 없군요. 
  var crustSize = 12 var crustType = "Thin" 
  def this(crustSize: Int) { // 매개변수 하나 받는 생성자~ 
     this() 
      this.crustSize = crustSize 
  } 
  def this(crustSize: Int, crustType: String) { // 매개변수 2개 받는 생성자 
       this(crustSize) 
       this.crustType = crustType 
  } 
  override def toString = { "A %s inch pizza with %s crust.".format(crustSize, crustType) } }

 

  println(new Pizza)
  println(new Pizza(14))
  println(new Pizza(16, "Thick"))

 

4) 생성자 매개변수에게 디폴트 값을~

 

class Socket (var timeout: Int = 10000) // 디폴트 값을 사용함 
val s = new Socket s.timeout // Int = 10000
val s = new Socket(5000) s.timeout // Int = 5000

 

5) 추상 클래스를 사용할 경우는 언제? 

 

추상클래스는 생성자 매개변수를 가질수 있지만, trait 는 그럴 수 없다. 
 * trait 는 자바의 인터페이스 비슷한 것 으로 추후에 설명 예정.

 

// 컴파일 안됨 trait Animal(name: String) 
// 추상 클래스는 가능 ! abstract class Animal(name: String)

 

abstract class BaseController(db: Database) {
  def save { db.save }
  def update { db.update }
  def delete { db.delete }
  def connect                             // abstract since it has no implementation
  def getStatus: String                   // an abstract method that returns a String
  def setServerName(serverName: String)   // an abstract method that takes a parameter
}

 

6) 스칼라 case 클래스 

 case 클래스는  유용한 행사코드를 자동으로  생성할 수 있다.

case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로

 

case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로

case class 를 사용하면 다양한 혜택과 자동으로 다음을 만들어 준다:

  • toString, equals, 와 hashCode  메소드를 만들어 준다.
  • 생성자 파라미터들에게 게터와 세터 속성이 부여된다.
  • 더이상 인스턴스로 만드는데 new 를 사용할 필요가 없다.
  • match/case 문에서 편리하게 이용할 수 있다.

 

while

 
스칼라에서 While,For 문은 없다로 생각하는게 좋습니다. 
개인적으론 Java 8 이상에서도 while 문은 없다라고 생각하는게 좋지 않나 합니다. 

아래와 같이 다른 방법 

Stream API

LINQ 

STL algorithm

컬렉션의 고차함수(map , flatmap,  filter, zip, fold, foreach, reduce, collect, partition, scan, groupBy 등) 

 

을 먼저 생각하는게 좋습니다

 

특징

* 스칼라의 while 은 다른 언어와 마찬가지로 동작합니다.

* if 나 for 가 표현"식" 인 반면에 while 은  "식" 이 아닙니다. 그냥 루프입니다.  즉 값을 내어 놓지 않습니다.

  예를들어 if 표현식의 경우 값을 내놓기 때문에 아래와 같이 코딩이 가능합니다.

  val filename = if (!args.isEmpty) args(0) else "default.txt"         

 

예제 

// 20 번 돌겠군요.  

var a = 0;

while( a < 20 ){

  a = a + 1;

}

 보통 while 문들 처럼  조건을 검사하고 조건이 참일때까지는 계속 반복 수행을 합니다. 

 

do while 루프도 있습니다.

var a = 0;
do { 
	a = a + 1; 
} while( a < 20 )

조건에 상관 없이 적어도 한번은 실행 할 수 있겠네요. ( 위의 코드에서는 20번 )

 

Unit 타입

루프의 결과는 그 타입이 Unit 입니다. 이 타입은 유니트 값 밖에 없으며 빈 괄호 () 로 표시합니다.

() 란 값이 존재한다는 점에서 자바의 void 와 다릅니다. 

def greet() { println ("hi")} 를 실행하면 () 이며  var 변수에 대한 재 할당도 () 가 결과가 됩니다. 

var line = 0;

while ((line = readLine()) != "") // 제대로 작동하지 않음 !!

println ("Read: " + line)

( line = readLine() )  를 평가하면 () 가 나오는데  != "" 이런식으로 비교하면 언제나 참입니다.

자바의 경우 할당받은 결과 값이겠지만,  스칼라에서 할당의 결과는 유니트 값인 () 이기 때문입니다.

따라서 

import java.io.File import scala.io.Source  

Source.fromFile(new File("myfile.txt")).getLines.foreach { 
	line => println(line) 
}

이런 방법을 고려해야 합니다. 사고 방식이 바뀌어야 한다는 말입니다.
이런식의 해결은 지양하고 있습니다.

while( {line = reader.readLine();  line!= null} ) { .... }

 

While 에서 탈출하기 

 2.8 버전부터 Breaks 가 생겼네요.

import scala.util.control.Breaks._ 

var largest = 0 // pass a function to the breakable method breakable 
{      
	for (i<-999 to 1  by -1; j <- i to 1 by -1) {         
    	val product = i * j         
        if (largest > product) {             
        		break  // BREAK!!        
        }        
        else if (product.toString.equals(product.toString.reverse)) {             
        	largest = largest max product         
         }     
     } 
 }

순수 함수형 언어에서 while

순수 함수형 언어에서는 while 의 결과가 특정 값을 내보내는게 아니기 때문에 종종 제외합니다. 그런 언어에서 "루프" 는 없습니다.하지만  스칼라에서는 존재하는데 때로는 명령형의 해법이 가독성이 더 뛰어나다고 믿기 때문입니다. 

 

재귀를 통한 while의 대체 

재귀와 while 은 공통점이 몇가지 있습니다.둘다 body 를 반복한다는 점이구요. 콘디션을 점검해서 순회의 종료를 알린다는 점입니다. 
위의 최대공약수 예제를 재귀로 바꾸어 보겠습니다.

// 최대 공약수 구하기
 

def gcd (x : Long, y :Long) : Long = 

if (y == 0) x else gcd (y, x%y) 

while 문의 컨디션 점검하는위치가 body 내부로 들어왔다는 점하고 

함수이름을 호출함으로써 순회가 돌아진다는 점이 조금 달라졌을 뿐입니다. 

* var 함수사용이 없어졌습니다.

일반적으로 while 루프틑 var 변수와 마찬가지로 적게 사용하기  위해 노력할것을 권장합니다.

 

while vs 재귀 vs 콜렉션 (고차함수) 

이 3가지에  관한 글을 링크했습니다.

 http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html

http://www.scala-lang.org/old/node/8113.html

http://alvinalexander.com/scala/scala-recursion-examples-recursive-programming

http://stackoverflow.com/questions/12496959/summing-values-in-a-list

http://stackoverflow.com/questions/18674743/is-there-any-advantage-to-avoiding-while-loops-in-scala

http://stackoverflow.com/questions/15138624/scala-recursion-vs-loop-performance-and-runtime-considerations

http://stackoverflow.com/questions/18645936/is-recursion-in-scala-very-necessary

 

 

Map

 
특성
 

* Map 은 변경 가능한 것 (mutable.Map) 과 변경 불가능한 것 (immutable.Map) 모두를 제공합니다.

import scala.collection.mutable.Map

  예를들어 put 이라든지 remove 메소드는 immutable.Map 에서는 사용 불가.

* Map 을 위한 기반 트레이트가 있고 이를 상속한 변경 가능 집합, 변경 불가능 집합을 위한 2가지 트레이트가 있다. ( Set 과 비슷) 


 
 
생성 
 
 

val m = Map[Int,String]()   // 다른것들과 마찬가지로 팩토리 메소드를 이용해 만들 수 있다.

 
val m2 = Map (1 -> "one" , 2-> "two") 이렇게 초기화 할 수 있고
val m3 = Map ( (1,"one") , (2,"two") ) 이렇게도 할 수 있다. 
 
아래 처럼  List 를 이용해서도 다양하게 가능해요~
// 튜플로 이루어진 List 를 이용 
val myList = List("England" -> "London", "Germany" -> "Berlin") 
// List 에 요소들이 Tuple 로  
val myMap = myList.groupBy(e => e._1).map(e => (e._1, e._2(0)._2)) 
val betterConversion = Map(myList:_*) 
val scala28Map = myList.toMap  // scala 2.8 version 에선 이렇게도 가능   
// map 메소드를 이용  
val myList = List("England" , "London", "Germany" ,  "Berlin") 
val map: Map[String,Int] = myList.map{ s => (s,s.length) }.toMap

 

추가

val m = mutable.Map[Int,String]()

m(0) = "zero"  //기존에 0 의  key 가 있을 경우 대체됩니다.  ( mutable.Map  일 경우만 ) 

아래 처럼도 가능 ( mutable.Map  일 경우만 ) 
m += ( 1 -> "one")
m += ( 2 -> "two")
+= 메소드를 이용하여 맵에 값을 담습니다.
* 위의 m 을 var 로 선언하면 immutable.Map 에서도 가능합니다.
 자신을 변경하는게 아니라 
변경된 맵을 새로운 m 에 재 할당하는 방식이 되니까요.

immutable 맵일 경우는
val m2 = m + (3 -> "three") 이런식으로 새로 생성하며 넣습니다.
 
1->"one" 
이것은  1 은 key 이며 "one" 은 value 입니다.
1.->("one")  이것의 축약형 입니다. 
 
스칼라에서는 -> 메소드를 호출하면 해당객체를 key 로하고 인자를 value 로 하는 
"튜플" 을 만듭니다. 
 

put mutable.Map  일 경우만 ) 

val m2 = m.put(2, "three") 
m에는 새로운 value 가 담기게 되고, m2 에는 기존에 key 가 2 였던 value 가 담기게 됩니다.  
 
m.getOrElseUpdate(2, "three") 

키 2 가 이미 있다면 그에 해당하는 값을 가져오고 , 없으면 "three" 를 추가.

 
 
가져오기
 
val m = Map[Int,String]()
m += ( 1 -> "one")
m += ( 2 -> "two")
 
println(m(1)) //  "one" 출력,  key 가 없을 경우 예외 발생 ~!!
 
 
get
 
val m = Map (1 -> "one" , 2-> "two")
m.get(1) 
 
key 가 1 인 value 를 옵션에 넣어 반환합니다. 없으면 None 을 반환  
 
m.get(3) // None
m(3) // 예외 !!  
 
 
 
제거 
 
m -= 1    // m 에서 key 가 1 인것을 제거 
 
remove  ( mutable.Map  일 경우만 ) 
val m2 = m.remove(1)  // m 에서 1을 key 로 가진 요소를 제거하고 그 값을 리턴
 
clear      mutable.Map  일 경우만 ) 
m.clear()  // m 의 모든 요소를 제거
 
retain     mutable.Map  일 경우만 ) 

m.retain((k,v) => k < 2)  // key 가 2 보다 작은 요소들만 남겨두고 나머지 제거

 

 

 
변환

 

스스로는 변화되지 않는데 , 변환된것을 리턴해준다.

filterKeys        ( immutable.Map  도 가능)

val m2 = m.filterKeys(_ > 1)    // key  가 1 보다 큰 요소들을 가지고  m2 를 만듬. 

 

mapValues     ( immutable.Map  도 가능)

val m2  = m.mapValues(_ * 5)  // 모든 value 에 * 5 를 한 요소를 가지고 m2 를 만듬 

 

 

변경 

 

transform   ( mutable.Map  일 경우만 ) 

m.transform( (k,v) => v * 5 )  // 모든 value 에 * 5 를 한 요소를 가지고 m2 를 만듬 

위의 mapValues 와 다른점은 transform 은 스스로도 변경된다는 점이다. 

Map -> mutable.Map 으로 변경 

 

val myImmutableMap = collection.immutable.Map(1->"one",2->"two") val myMutableMap = collection.mutable.Map() ++ myImmutableMap

 

Set

 
특성
 

* Set 은 변경 가능한 것과 변경 불가능한 것 모두를 제공합니다.

* Set 을 위한 기반 트레이트가있고  이를 상속한 변경 가능 집합, 변경 불가능 집합을 위한 2가지 트레이트가 있다.

   ( 트레이트는 자바 인터페이스와 비슷한 것으로 나중에 설명 ) 

 
 
 
생성 
 

val s  = Set ("Hi" , "There") 

리스트나 배열과 비슷하게 생성한다. 

변경 불가능한 Set 객체를 만들어서 변경 불가능한 s 에 대입한다.

 
 

변경

 

import scala.collection.immutable.set

 
var  s  = Set ("Hi" , "There")
s += "bye" 
 
이 코드에서 눈여겨 볼것은 var 로 선언된 s 이다.  이 코드는
기존 s 에 "bye" 를 더하면서 새로운 집합으로 재 할당 한것이다.
 
즉 s = s + "bye" 에서 두 s  가 가르키는 객체는 다른 것이다. 
새로 s 를 할당받기 위해서 var 로 선언했던것이다.
*  val 로 선언하면 += 에서 에러!!
 
근데 
import scala.collection.mutable.set 으로 선언을 해주면
변경 가능한 Set 를 사용할 수 있다.
 
이때는 
val s = Set("Hi", "There")
s += "bye" 

라고 하면 새로운 할당이 일어나지 않고  Set자체에 추가한다. 

 

 

'Scala' 카테고리의 다른 글

스칼라 강좌 (7) - 컬렉션과 자바  (0) 2016.06.26
스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13

Tuple

 
특성

 

* 리스트와 마찬가지로 변경 불가능 하다.

* 리스트와 다른 점은 다른 타입의 원소를 넣을 수 있다는 점

메소드에서 여러 다양한 객체를 리턴해야 하는 경우 유용하다. 

 

 
생성 
 

val p = ( 99, "High") 

그냥 객체들을 콤마로 구분하여 () 사이에 넣어주면 된다. 
저것의 타입은 Tuple2[Int,String] 이다.
튜플의 타입은 원소의 개수와 각각의 타입에 따라 달라진다.  (22 개 까지 지원)
즉 

('u','r',"the") 의 타입은  Tuple3[Char,Char,String]이다.

 

 

원소에 접근 

println(p._1)  

튜플의 첫번째 값에 접근할수있다.

 

 

마지막 

왜 튜플은 원소접근을 p(0) 즉 p.apply(0) 처럼 할 수 없을까?

-> 리스트의 apply 메소드는 항상 동일한 타입의 객체를 반환하는 반면, 튜플의 각 원소들은 타입이 각기 다를 수 있기 때문

 

* 주의 사항

튜플은 각 원소의 접근을 0 이 아니라 1 부터 시작한다.

 

'Scala' 카테고리의 다른 글

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

이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한   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

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

또한 이 언어는 하이브리드입니다. 함수형과 객체지향 양쪽을 지원합니다. 함수형을 추구하되 객체지향도 쓸 수 있다 정도입니다만, 덕분에 굉장히 강력해 질 수도 혹은 복잡할 수 있습니다. 딱 맞는 예는 아니지만 마치 하이브리드 객체지향인 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