Scala Tutorial 정리

biosina1·2020년 5월 21일
0
post-thumbnail

Scala 란?

Scala는 범용, 고급, 다중 패러다임 프로그래밍 언어이다. 함수형 프로그래밍 방식을 지원하는 순수한 객체 지향 프로그래밍 언어이다. 바이트 코드로 변환 할 수 있으며 JVM에서 실행된다.

Scala 특징

1) 객체지향 : Scala의 모든 값은 객체이므로 순수한 객체 지향 프로그래밍 언어이다.
2) 함수형 : 모든 기능은 값이고 모든 값은 객체이므로 함수형 프로그래밍 언어이다.
3) 확장성 : 새로운 언어 구조를 라이브러리 형태로 추가 할 수 있다.
4) 동시 및 벙렬 처리 : Scala를 사용하면 벙렬 처리 및 동시성을 쉽게 수행할 수 있는 변경 불가능한 방식으로 코드를 작성한다.

Scala Tutorial 시작

변수

변수는 단순히 저장 위치이며 Scala에는 두 가지 유형의 변수가 존재한다.

  • var ( 가변 변수 ) : 변수 선언 후 값을 변경할 수 있다.
  • val ( 불변 변수 ) : 변수 선언 후 값을 변경할 수 없다.
// 가변 변수
var name : String = "nsh";

// 불변 변수
val name : String = "nsh";

함수

1) 함수 정의

함수 정의는 def로 하며 매개 변수에 타입을 꼭 명시해야한다.

def functionName(a: Int, b: Int) = a + b

2) 함수 호출

매개 변수가 없는 함수 호출은 괄호를 생략할 수 있다.

// 괄호 생략 불가능
def functionNameA(a: Int, b: Int) = a + b
functionNameA() // compile error
functionNameA(3, 6)

// 괄호 생략 가능
def functionNameB() = println("functionNameB")
functionNameB
functionNameB()

3) 익명 함수

이름이 없는 함수를 만들 수 있으며, 이름 없는 함수를 다른 함수나 식에 인자로 넘기거나 변수에 저장할 수 있다.

// 이름 없는 함수
(x: Int) => x + 5

// 변수에 이름 없는 함수 저장
val functionNoName = (x: Int) => x + 5

4) 함수가 여러 식으로 이루어진 경우

// 하나의 식
def function(x: Int): Int => x+5

// 여러 식
def function(x: Int): Int => {
    println("x : " + x)
    return x
}

5) 부분 함수(Partial application)

여러개의 인자를 받는 함수가 있을 때 일부의 인자를 고정한 함수를 만드는 것이 부분함수 이다.
Scala에서는 '_'를 사용해서 일부의 인자를 고정한 새로운 함수를 생성한다.

def partialFunction(x: Int, y: Int) = x + y; // partialFunction 정의
val add10 = partialFunction(10, _: Int)      // x인자를 10으로 고정한 새로운 함수를 얻어서 add10 변수에 저장한다.
add10(5)                                     // 인자를 넣어주면 10을 더한 결과값 리턴

6) 커리 함수(Curried functions)

커리 함수도 부분 함수처럼 인자를 고정할 수 있지만 하나씩 고정하는것이 특징이다. 모든 인자를 다 받을때 까지 계속 함수를 생성한다.

def curriedFunction(w: Int)(x: Int)(y: Int)(z: Int): Int = w + x + y + z
val curried1 = curriedFunction(1) _
val curried2 = curried1(2)
val curried3 = curried2(3)
val result = curried3(4) // 10

클래스(Class)

Scala에서 클래스 객체를 만들기 위한 설계도이다. 클래스 맴버라고 통칭하는 변수, 메서드, 객체, 트레잇, 클래스를 포함할 수 있다.

1) 클래스 생성

  • 가장 단순한 클래스 생성
class User
var user = new User

new 키워드는 클래스의 인스턴스를 만들기 위해 사용하며, 생성자를 정의하지 않았기 때문에 인자가 없는 기본 생성자를 갖는다.

  • 기본 클래스 생성

class Point(var x: Int, var y: Int) {
    
    // 두 개의 정 수 인자를 취하여 정보를 전달하지 않는 Unit(Java의 void와 유사) 타잆의 값 ()반환
    def move(dx: Int, dy: Int): Unit = {
    	x = x + dx
        y = y + dy
    }
    
    // override를 지정해서 기존의 toString을 대체
    override def toString: String = s"($x, $y)"
}

val point1 = new Point(2, 3);
point1.x // 2
println(point1) // (2,3)

2) 생성자

  • 생성자 기본 값 제공
class Point(var x: Int = 0, var y: Int = 0) { 
  override def toString: String = s"($x, $y)"
}
val point1 = new Point(1)
point1.toString() // (1, 0)

val point2 = new Point(y=2);
point2.toString() // (0, 2)

3) Private 멤버와 Getter/Setter

멤버는 기본적으로 public으로 지정된다. private 접근 지시자로 클래스 외부로부터 멤버를 숨길 수 있다.

Setter 메소드의 문법에 주목해보자. Getter 메소드 이름에 '_'를 붙여주면 Setter 메소드가 된다.

class Point {
  private var _x = 0;
  private var _y = 0;
  private var bound = 100;
  
  def x: Int = _x  // private data _x에 접근 (getter)
  def y: Int = _y  // private data _y에 접근 (getter)
  
  //private data _x에 값 검증 및 설정
  def x_= (newValue: Int):Unit = { // 
    if(newValue < bound ) _x = newValue else printWarning
  }
  
  //private data _y에 값 검증 및 설정
  def y_= (newValue: Int):Unit = { 
    if(newValue < bound ) _y = newValue else printWarning
  }  
  def printWarning() = println("out of bounds")
}

var point1 = new Point
point1.x
point1.x = 111

클래스(class) 상속

  • Scala는 다중 상속이 아닌 단일 상속을 지원한다. 하위 클래스는 하나의 상위 클래스만 가질 수 있다.

  • 메소드 재정의는 override 키워드를 사용한다.

class Vehicle(speed: Int) {
  val mph: Int = speed
  def race() = println("Racing")
}

new Vehicle(100);

class Car (speed: Int) extends Vehicle(speed) {
  override val mph: Int = speed
  override def race(): Unit = println("Racing Car")
}

new Car(100).race()

추상 클래스(Abstract Class)

  • 추상 클래스는 메소드 정의는 있지만 구현은 없는 클래스이다. 대신 이를 상속한 하위클래스에서 메소드를 구현한다.
  • 추상 클래스의 인스턴스는 만들 수 없다.
abstract class Shape() {
  def getArea(): Int
}

// compile error, abstract class는 인스턴스 생성 불가
new Shape 

class Circle(r: Int) extends Shape {
  def getArea() = r * r * 3
}

var circle1 = new Circle(4)
circle1.getArea() // 48

트레잇(Trait)

  • Trait은 Java의 인터페이스와 추상클래스의 장점이 섞였다.
  • Trait은 정의만 가능한 Java의 인터페이스와 다르게 정의 및 구현이 가능하다.
  • 여러개의 trait은 'with' 을 사용한다. (mix in)
// sound 메소드 정의 및 구현
trait engine {
  def sound() : Unit = println("부우우웅")
} 

class Car extends engine

val car1 = new Car
car1.sound()

------------------------------------

// sound 메소드 정의 및 상속받은 클래스에서 구현
trait engine2 {
  def sound() : Unit
} 

class Car2 extends engine {
  override def sound(): Unit = println("부우우웅2")
}

val cal2 = new Car2
cal2.sound()

------------------------------------
// 여러개의 trait을 사용 (mix in)
trait Color {
  val mainColor: String
}

trait Engine {
  def sound() : Unit = println("부우우웅")
  def sportModeSound(): Unit
} 

trait Option {
  val isSunroof = true
  val wheelSize = "17"
}

class Car3 extends Engine with Option with Color {
  override val mainColor = "red"
  override def sportModeSound(): Unit = println("뿌우우웅")
}

val cal3 = new Car3
cal3.mainColor

cal3.sound()
cal3.sportModeSound()

cal3.isSunroof
cal3.wheelSize

apply ( 작성중 )

  • 클래스 및 객체의 용도가 주로 하나만 있는 경우 멋지게 표현할 수 있다.
  • apply를 정의하면 인스턴스 호출이 가능하고 호출되면 그 객체(클래스)에 정의된 apply 메소드가 호출된다.

    자세한 것은 나중에 추가.....

// 객체 호출 시 apply 메소드 호출
class Foo {} 

object FooMaker {
  def apply(): Foo = new Foo
}

val foo = FooMaker() // Foo@4cca7332


// 인스턴스 호출 시 apply 메소드 호출
class Bar {
  def apply(): Int = 0
}

val bar = new Bar
bar() // 0

객체

  • Scala에는 정적(static)맴버가 존재하지 않는다. 대신 싱글톤 객체를 제공한다.
  • 싱글톤 객체는 대부분 독립적이지 않고, 같은 이름의 클래스와 연관되어 있다. 이를 클래스의 '동반자 객체'라고 부른다.
  • 싱글톤 객체만 정의된 경우에는 new 로 객체를 생성할 수 없다.
  • 하나의 클래스와 동반자 객체는 같은 소스파일에 정의되어야 한다.
object Timer {
  var count: Int = 0
  def currentCount: Long = {
    count += 1
    return count
  }
}

Timer.currentCount // 1

// 동반 객체, 동반 클레스
class Bar(foo: String)
object Bar {
  def apply(foo: String) = new Bar(foo)
}

val bar = new Bar("foo")
println(bar) // Bar@......

패턴 매칭

  • 패턴 매칭은 match를 사용한다.
  • Top-Down으로 매칭
// 값 매치
var num1 = 1
num1 match {
  case 1 => "일"
  case 2 => "이"
  case _ => "some other num"
}


// 조건문 매치
var num2 = 2
num2 match {
  case n if n == 1 => "일"
  case n if n == 2 => "이"
  case _           => "some other num"
}

// 타입 매치
def typeMatch(o: Any) = o match {
    case i: Int    => "Int Value"
    case d: Double => "Double Value"
    case s: String => "String Value"
    case _         => "unkwon Value"
}

typeMatch("nsh") // "String Value"


// 클래스 맴버 매치
class TestClass {
  val color: String = "red"
  val size: String = "20G"
}

val testClass = new TestClass

def classMemberMatch(test: TestClass) = {
  val value = test.color
  test match {
    case t if t.color == "red" => "success red"
    case t if t.color == "blue" => "success blue"
    case _ => "fail"
  }
}

classMemberMatch(testClass)

케이스 클래스(Case Class)

  • 기본적으로 불변 변수가 선언된다.
  • 초기화가 간단하다.
  • 인스턴스를 생성할 때 new를 사용하지 않는다.
  • 보일러플레이트 코드를 제공한다. ( getter methods, hashCode, equals, copy method, apply, unapply, toString )
case class Person(name: String, age: Int)

// 초기화 간단하다.
val person = Person("nsh", 27);

// 불변 변수로 자동 선언
person.name = "nsh2" // error : reassignment to val

val person1 = Person("nsh", 27);
val person2 = Person("nsh", 27);
val person3 = Person("nsh", 27);
val person4 = Person("nsh", 28);

// 참조 값이 아닌 클래스 맴버 변수의 값으로 비교
person1 == person2 // true
person3 == person4 // false

// 보일러플레이트 코드
person1.equals(person2) // true
person1.hashCode()
val person5 = person1.copy("nsh2", 29)
person1.toString()

예외

  • try - catch - finally
var tempTry: Any = try { 
  val result = 11/0 
} catch {
  case e: Exception => println(e)
} finally {
  println("finally")
}

List(리스트)

  • List는 값을 변경할 수 없다.
  • 부수효과가 일어나지 않아서 더 신뢰하고 재사용하기 쉬운 코드를 만든다.
// List 생성
val list1: List[Int] = List.range(5, 10) // List(5,6,7,8,9)
val list2: List[String] = List.fill(5)("nsh") // List(nsh,nsh,nsh,nsh,nsh)
val list3: List[Int] = List.tabulate(4)(n => n * n) // List(0,1,4,9)

// List 원소 가져오기
list1(0) // 5
list2(1) // nsh
list3(2) // 4

/*
List 앞에 요소 추가
List는 값을 변경할 수 없으므로 값이 추가된 새로운 List를 만듦
*/

val newList1 = 1 :: list1 // List(1, 5, 6, 7, 8, 9)

// 두개의 리스트 합치기 
val newList2 = list1 ::: list2 // List(5, 6, 7, 8, 9, nsh, nsh, nsh, nsh, nsh)
val newList3 = list1.concat(list2) // List(5, 6, 7, 8, 9, nsh, nsh, nsh, nsh, nsh)


/* 
List 뒤에 요소 추가
:+은 리스트의 길이만큼 오래걸린다. 
1. 리스트를 뒤집고 앞에 원소를 추가하고 다시 뒤집는다.
2. 새 리스트로 연결 시킨다.
3. ListBuffer, ArrayBuffer 사용 ( 나중에... )
*/
var newList4 = list1:+4


// List 길이
list1.length // 5

Set(집합)

  • 불변Set, 가변Set 모두 제공
  • Set은 크기에 따라 별도의 클래스가 있다. Set1,Set2,Set3,Set4 구성요소가 5개 이상이면 HashSet
  • Set은 집합에 대응하여 같은 값을 추가하면 기존 값을 덮어쓴다.
  • 순서가 보장되지 않는다.
/*
import scala.collection.immutable.set
내용을 수정할 수 없는 Set
같은 값을 추가하면 기존 값을 덮어씀 ( 중복X )
*/
val set1 = Set(1,2,3,4,"set") // HashSet(1, set, 2, 3, 4)
val set2 = Set(1,1,2,2,3,3,4,"set") //HashSet(1, set, 2, 3, 4)
set1 += 5 // error


// 내용을 수정할 수 있는 Set
var set3 = Set(1,2,3)
set3 += 4 // Set(1, 2, 3, 4)

Tuple(튜플)

  • 불변하다
  • 다른 타입의 데이터를 포함할 수 있다.
// Tuple 생성
val tuple: Tuple2[String, Int] = ("nsh", 27)

// Tuple 엘리먼트 접근
tuple._1 // nsh
tuple._2 // 27

// 튜플에서 패턴 매칭
val ( name, age ) = tuple;
name // nsh
age  // 27

// 튜플 매칭
tuple match {
  case ("nsh", 27) => println("match success")
  case _ => println("match fail")
}

// 두 값을 튜플로 만드는 방법
"localhost" -> 8080

Map(맵)

  • 가변 Map, 불변 Map 모두 제공
  • pust, remove 등의 메소드는 불변 Map에서 사용 불가

// 생성
val m = MapInt, String;
val m2 = Map(1 -> "one", 2 -> "two")
val m3 = Map((1, "one"), (2, "two"))
var m4 = scala.collection.mutable.MapInt, String

/*
추가 : 가변 Map에서만 추가 가능
getOrElseUpdate : 해당 값이 있으면 가져오고, 없으면 추가
*/ 
m4(0) = "zero"
m4 += ( 1-> "one" )
m4.put( 2, "two" )
m4.getOrElseUpdate(3, "three")

/*
가져오기 
m4.get(2) : 값이 없으면 None 반환
m4(2) : 값이 없으면 Error
*/
m4.get(1)
m4(1) // error 

/*
제거 : mutable.Map
remove: 해당 키 요소 제거
clear : 모든 요소 제거
retain: key 보다 작은 요소들을 남겨 놓고 제거
*/
m4.remove(0)
m4.retain((k, v) => k < 2 )
m4.clear()

Option(옵션)

  • 값이 있거나, 없거나 한 상태를 나타내는 타입
  • Option의 하위 타입은 Some[T], 값이 없으면 None
  • Option은 Try, Future등 과 함께 표현
  • null에 대한 예외상황을 방어한다.
  • 연속 계산에서 안정적으로 실행하기위해 사용한다.
/*
1. Option 적용
val two는 값이 존재하므로 Some(2) 반환
val three는 값이 존재하지 않지만 Option을 적용해서 Error가 아닌 None을 반환
*/

val two: Option[Int] = num.get("two"); // Some(2)
val three: Option[Int] = num.get("three") // None


/*
2. Option 미적용
val four은 Option 미적용으로 error 발생
*/

var four: Int = num.get("four") // error: type mismatch


/*
3. 
isDefined : 값이 있으면 true, 없으면 false
isEmpty : 값이 없으면 true, 있으면 false
*/

if (three.isDefined) {
  println("not None")
}

if (three.isEmpty) {
  println("None")
}


// 4. getOrElse : 값이 있으면 그 값을 사용하고, 없으면 인자로 넘긴 값을 사용
def testOptionGetOrElse(o: Option[Int]): Unit = {
  println(o.getOrElse("nothing"))
}

testOptionGetOrElse(three) // nothing


// 5. 패턴 매칭

def testOptionMatch(o : Option[Int]): Any = o match {
  case None => "nothing"
  case Some(n) => n
} 

testOptionMatch(Some(5)) //5
testOptionMatch(None)    //nothing

Either

계속해서 작성중......

profile
잘 하고 싶다.

0개의 댓글