[Scala 정리] 기본

Chan hae OH·2023년 6월 26일
0

Scala 기초

목록 보기
1/1



프로그래밍 스칼라 책의 내용을 정리하고 있습니다.


1. 세미콜론


세미콜론은 예제를 구분하는 구분자며, 스칼라는 이를 추론한다. 스칼라는 한 줄의 끝에서 식을 다음 줄로 계속 이어가야 한다고 추론하지 않는 경우, 줄 끝을 식의 끝으로 취급한다.

// 줄이 등호로 끝나면 다음 줄에 이어지는 코드가 더 있다는 뜻이다.
def equalsign(s: String) = 
	println("equalsign: " + s)
 
// 줄이 중괄호를 열고 끝나면 다음 줄에 이어지는 코드가 더 있다는 뜻이다.
def equalsign2(s: String) = {
	println("equalsign2: " + s)
}

// 줄이 쉼표, 마침표, 연산자로 끝나면 다음 줄에 이어지는 코드가 더 있다는 뜻이다.
def commas(s1: String,
			s2:String ) = Console.
    println("comma: " + s1 + 
    		", " + s2)



2. 변수 정의


스칼라에서는 변수가 불변(읽기 전용)인지 아닌지(읽기-쓰기) 선언 시 지정할 수 있다.

val array: Array[String] = new Array(5)

array 라는 참조는 다른 Array를 가리키도록 재할당될 수 없다. 하지만 배열의 원소는 다음과 같이 변경 가능하다.

val array = Array[String] = new Array(5)

// error 발생, val 은 재할당이 불가능하다.
array = new Array(2)

array(0) = "Hello"

array

var 라는 키워드는 변경 가능한 변수로 선언할 수 있다. 그럼에도 반드시 선언할 때 초기화 해야 한다.

var stockPrice: Double = 100.0

strockPrice = 200.0

var 변수는 값을 바꿀수 있지만 객체(타입)를 바꿀 수 없다.

class Person(val name : String, var age : Int)

val p = new Person("oh", 29)

// 에러 발생
p.name = "jeong"

p.age = 30

변경으로 인해 생길 수 있는 버그 유형을 방지하기 위해 가능한 한 변경 불가능한 값을 활용하는게 좋다. 예를 들어 해시 기반의 맵에 변경 가능한 객체로 키를 사용하는 것은 위험하다. 해당 객체의 내용이 바뀌면 hashCode 메서드의 반환값이 달라질 것이다.

사용중인 객체를 다른 누군가가 변경해서 생기는 예기치 못한 동작은 매우 치명적이다. 특히 공유된 변경 가능한 상태에의 접근을 동기화할 필요가 있는 다중 스레드 프로그램에 가장 치명적이다.

처음 부터 변경 불가능한 값을 사용한다면 이런 문제를 방지할 수 있다.



3. 범위


Range 객체를 사용하여 범위를 만들어 낼 수 있다.

1 to 10
// Range(1,2,3,4,5,6,8,9,10)

1 until 10
// Range(1,2,3,4,5,6,7,8,9)

1 to 10 by 3
//Range(1,4,7,10)

10 to 1 by -3
//Range(10,7,4,1)

1L to 10L by 3
//NumericRange(1,4,7,10)

1.1f to 10.3f by 3.1f
//NumericRange(1.1, 4.2, 7.2999997)

1.1f to 10.3f by 0.5f
//NumericRange(1.1, 1.6, 2.1, 2.6, 3.1, 3.6, 4.1, 5.1, 5.6, 6.1, 6.6, 7.1, 7.6, 8.1, 8.6, 9.1, 9.6, 10.1)

1.1 to 10.3 by 3.1
//NumericRange(1.1, 4.2, 7.30000000001)

'a' to 'g' by 3
//NumericRange(a, d, g)

BigInt(1) to BigInt(10) by 3
//NumericRange(1, 4, 7, 10)

BigDecimal(1.1) to BigDecimal(10.3) by 3.1
//NumericRange(1.1, 4.2, 7.3)



4. 부분 함수


부분 함수에서 부분이란 말은 함수가 모든 가능한 입력에 대해 결과를 정의하지는 않는다는 뜻이다. 지정한 케이스 절에서 어느 하나와 일치하는 입력에 대해서만 결과를 정의한다.

부분 함수 안에는 케이스 절만 들어갈 수 있고 반드시 전체를 중괄호로 묶어야 한다. 반면 '일반적' 함수 리터럴은 중괄호뿐 아니라 괄호로도 본문 식을 묶을 수 있다.

케이스 절의 하나와 일치하지 않는 값이 부분 함수의 인자로 들어오면 MatchError tlfgod tlwja dPdhlrk qkftodgksek.

부분 함수, 즉 PartialFunction이 어떤 입력과 일치하는지는 isDefinedAt 메서드를 호출해서 알 수 있다. 이 메서드를 사용해서 MatchError 예외를 발생시킬 위험을 없앨 수 있다.

pf1 orElse pf2 orElse pf3 ... 등과 같이 PartitalFunction 을 여럽 '연쇄 호출' 할 수 있다. pf1과 일치하지 않으면 pf2를 시도하고, pf2가 실패하면 또 pf3 를 시도하는 식이다. MatchError는 모든 부분 함숙 ㅏ입력과 일치하지 않는 경우에 발생한다.

val pf1: PartialFunction[Any, String] = { case s:String => "YES" }
val pf2: PartialFunction[Any, String] = { case d:Double => "YES" }

val pf = pf1 or Else pf2

def tryPF(x: Any, f: PartialFunctionp[Any,String]): String = try { f(x).toString } catch { case _: MatchError => "ERROR!" }

def d(x: Any, f: PartialFunction[Any, String]) = f.isDefinedAt(x).toString

println(" 	    |  pf1 - String  |   pf2 - Double |	pf - ALL")
println(" x    | def?  | pf1(x) | def2? | pf2(x) | def?  | pf(x)")
println(" ++++++++++++++++++++++++++++++++++++++++++++++++++++++")
List("str", 3.14, 10).foreach { x =>
	printf("%-5s | %-5s | %-6s | %-5s | %-6s | %-5s | %-6s\n", x.toString, d(x,pf1), tryPF(x, pf1), d(x,pf2), tryPF(x,pf2), d(x,pf), tryPF(x,pf))
}

// 	    |  pf1 - String  |   pf2 - Double |	pf - ALL
// x    | def?  | pf1(x) | def2? | pf2(x) | def?  | pf(x)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// str  | true  | YES    | false | ERROR! | true  | YES
// 3.14 | false | ERROR! | true  | YES    | true  | YES
// 10   | false | ERROR! | false | ERROR! | false | ERROR!



5. 메서드 선언


// copy 메서드를 사용하면 케이스 클래스의 기존 인스턴스를 복사하면서 일부 필드를 변경해서 새로운 객체를 만들 수 있다.
case class Point(x: Double = 0.0, y: Double = 0.0) {
	def shift(deltax: Double = 0.0, deltay: Double = 0.0) = copy(x + deltax, y + deltay)
}

val p1 = new Point(x = 3.3, y = 4.4)
// Point(3.3, 4.4)

val p2 = p1.copy(y = 6.6)
// Point(3.3, 6.6)

// 블록 구조 구문

abstract class Shape(){
/**
 * 두 인자 목록을 받는다.
 * 한 인자 목록은 그림을 그릴 때 x, y축 방향으로 이동시킬 오프셋 값
 */
 def draw(offset: Point = Point(0.0, 0.0))(f:String => Unit): Unit =
 	f(s"draw(offset = $offset), ${this.toString}")
}

case class Circle(center: Point, radius: Double) extends Shape

case class Rectangle(lowerLeft: Point, height: Double, width: Double) extends Shape


// 1.
s.draw(Point(1.0, 2.0))(str => println(s"ShapesDrawingActor: $str"))

// 2.
s.draw(Point(1.0, 2.0)){ str => println(s"ShapesDrawingActor: $str")}

// 3.
s.draw(Point(1.0, 2.0)){ str => 
	println(s"ShapesDrawingActor: $str")
}

// 타입 추론

def m1[A](a: A, f: A => String) = f(a)

def m2[A](a: A)(f: A => String) = f(a)

m1(100, i => s"$i + $i")
// error: missing paramter type i

m2(100)(i => s"$i + $i")



profile
Data Engineer

0개의 댓글