본문 바로가기

Programmer Jinyo/Scala & FP

스칼라 공부 정리 필기 노트


투명한 기부를 하고싶다면 이 링크로 와보세요! 🥰 (클릭!)

바이낸스(₿) 수수료 평생 20% 할인받는 링크로 가입하기! 🔥 (클릭!)

* 개인 공부 필기 노트 공유함니다..

* 혹시 필요한 정보를 얻어가신다면 기쁘겠읍니다....

 

스칼라의 object는 자바로 치면 static class 형식이다.

 

object 예약어는 class 자체를 싱글턴 객체로 만든다.

 

스칼라에서 Unit은 void이랑 같은 의미다.

 

스칼라의 자료형

 

Any {

  AnyVal {

    Byte,Short,Int,Long,Char,Unit,Boolean, .....

  },

  AnyRef {

    사용자가 만든 객체, AnyVal이 아닌 내장 객체

  },

  String

}

 

->

이를 통해서

var a:Int = 5

var b = a

a = 10

!!!이때 b는 여전히 5. 왜냐하면 b는 기본 자료형이기 때문에 5를 이미 카피해서 저장해 놓았기 때문.

 

반면에, 직접 계속 copy하는 것이 너무 부담스러운 경우 AnyRef를 통해 (참조 자료형) 포인터스럽게 시간을 아낄 수 있다.

 

type 예약어로 자료형 새로 만들기.

 

type Name = String

type Person = (String,Int)

type FType = String => Int

 

등과 같은 변경이 가능.

 

val name : Name = "아"

val person : Person = ("아",24)

val FType = text => text.toInt // text라는 입력을 받아서 숫자로 바꿔줌. ㅋㅋㅋ type에서 정의해도 자동으로 되는건 아닌거같고 그냥 함수 자체를 type으로 정의가 가능하다는 뜻인 것 같다.

 

 

 

스칼라의 for문

 

for( x <- 1 to 5){

  println("x의 값은"+x)

}

 

->결과

 

x의 값은 1

x의 값은 2

x의 값은 3

x의 값은 4

x의 값은 5

 

for( x <- 1 until 5){

  println("x의 값은"+x)

}

 

->결과

 

x의 값은 1

x의 값은 2

x의 값은 3

x의 값은 4

 

 

until은 이하, to는 미만

 

이중 for문 작성

for(x<-1 until 5){

  for(y<-1 until 5){

    ~~~

  }

}

 

이것은

 

for (x<-1 ultil 5; y<-1 until 5) {

  ~~~

}

 

와 동일하다.

 

 

조건을 넣은 반복문

 

for( i<- (1 to 10) if (i%2==0)) {

  ~~~

}

 

이런짓도 된다. i%2인 애들만 실행된다.

 

zip

 

for( (num,index) <- List.zipWithIndex ) {

  ~~~

}

 

List는 스칼라 컬렉션의 일부라고 한다.

List의 zipWithIndex메서드를 통해 zip 을 구현 가능하다.

 

 

클래스

 

class User (name:String , age: Int , sex : Char)

 

하고 나면

val user = new User ("철수",27,f) 로 인스턴스를 만들 수 있다.

 

recap : class : 객체의 틀 / new 로 만들면 그게 객체

 

+ object를 통해서 만들면 그 자체가 싱글턴 객체이다. 그 안의 모든 함수들은 Math.random()같이 호출이 가능하다.

 

다만 이렇게 축약 방식으로 class를 만들면 멤버 변수들이 모두 private 형식으로 선언되어서 바로 접근이 안된다.

 

케이스 클래스

 

케이스 클래스는 그냥 클래스에서 조금 더 기능을 확장한 클래스이다. 자동으로 public 멤버 변수를 만들어주며 객체의 정보를 알 수 있게 해주는 toString 이나 머 등등 자동으로 된다고 한다.

 

case class Fruit(name: String)

 

하면

 

val apple = Fruit("사과") // case class는 new 또한 생략 가능

println(apple.name) 일케 출력 가능.

 

상속

 

class User(name: String, age:Int, sex: Char){

  val sayName=println("제 이름은 "+name)

}

 

class PaidUser(name: String, age:Int, sex:Char, money: Int) extends User(name,age,sex) {

  val = showMoney = println(money + " 원이 있습니다.")

}

 

trait (트레이트 , 특성)

 

다중상속이 가능한 자바의 인터페이스와 비슷하지만, 동시에 구현을 해놓지 못하는 자바의 인터페이스와는 다르게 변수 선언이라던지 메서드 구현이 가능하다.

구현이 필요한 특성을 가지는 클래스를 다중상속 할 때는 trait가 답이다. (믹스인을 통해 할 수 있다구..)

 

 

trait Swimming {

  def swim = println("수영")

}

trait Flying {

  def fly = println("비행")

}

trait Running {

  def run = println("달리기")

}

trait Eating {

  def eat
}

 

class Animal extends Flying with Swimming // 이라던지

class Animal2 extends Eating {

  def eat = println("ㅁㅎㅇ")

}  // 이라던지

 

가능.

 

오버라이드 가 하고싶으면

 

class Animal3 Extends Flying with Swimming {

  override def fly = println("오버라이드 해벌이기")

}

일케 하면 된다.

 

믹스인(Mixin)

 

Scala의 믹스인은 extends로 단일 상속으로 하게 하고, with로 믹스인을 구현한다. 

다만 좀더 다양한 기능을 가진 동물들을 이렇게 매번 재정의 하는 것 보다

 

abstract class Animal {

  def shout 

}

 

해놓고

 

class Pig extends Animal with Flying with Eating {

  def shout = println("꿀꿀")

  def eat = printlnf("쩝쩝")
}

 

등과 같이 다중상속 냄새가 나게 정의가 가능하다.

 


스칼라의 함수

함수형 프로그래밍에서는 여러 상태를 바꾸어버리는 부수효과 (side effect)가 나타나는 함수를 지양하고 있다. 단순한 코드를 반복 사용하기 위한 것이 아닌 입력과 출력이 명확한 함수를 만들어야 한다. 자바스크립트처럼 함수 자체를 전달하고 반환받을 수 있음. (자바스크립트 같네!) 

 

선언 방법

 

def 함수명([매개변수]): [반환 자료형] = {

  // 구현 로직

}

 

그런데 반환 자료형도 생략이 가능하다. 아래와 같이

 

def main(args: Array[String]) = {

  println("블라블라" + name())

}

def name() = {

 val a = 10

 a

}

 

(ㄷㄷ 이러면 10이 리턴되나봄;;;ㄷㄷㄷㄷㄷ return을 명시적으로 쓰지 않아도 알아서 리턴한단다. 근데 난 리턴 쓸래;;)

 

 

또한

 

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

 

이렇게 중괄호 생략도 가능이다.

 

 

스칼라에서 함수 받아오기

 

def main(args: Array[String]) = {

  a(b(5))

}

def b(n:Int)={

  println("b실행")

  n

}

def a(n:Int)={

  println("a실행")

  println(n + "값 가져옴")

}

 

-> 실행결과

b실행

a실행

5값 가져옴

 

 

def main(args: Array[String]) = {

  a(b(5))

}

def b(n:Int)={

  println("b실행")

  n

}

def a(n: => Int)={

  println("a실행")

  println(n + "값 가져옴")

}

 

-> 실행결과

a실행

b실행

5값 가져옴

 

이것을 ( => n 형식) call-by-name이라고 하며 함수 자체를 부르는 방식이다.

(함수를 실행시키며 그 리턴값을 가져온다는 식으로 n을 선언한 것)

 

partially applied function (부분적으로 등록(적용)된 함수)

 

(partially applied function & partail function 에 대한 설명 글 링크)

https://blog.outsider.ne.kr/953

 

기본적으로 모든 함수의 인자를 받는것이 아닌, 함수의 인자가 일부만 적용되어 있는 함수를 부분 적용 함수라고 한다.

 

{

  val fixedValueFunction = go(2019, _:String)

  fixedValueFunction("aaa")

  fixedValueFunction("bbb")

}

def go(thisYear:Int, string :String) = {

  println(string+thisYear)

}

 

-> 실행결과

aaa2019

bbb2019

 

Partial function (부분 함수)

 

부분 적용 함수랑 아얘 다른 것이다.

 

X->Y로의 partial function은 X' -> Y이고 X'은 X의 서브셋이다.

 

 

 

커링(currying) 을 이용하면 여러개의 인수를 받는게 아니라 하나의 인수를 받는 여러개의 함수로 바꿔줄 수 있다.

def sumA(x:Int ,y:Int) = x+y

def sumB(x:Int)(y:Int) = x+y

 

위 go의 경우 이러면

fixedValueFunction = go(2019)_

라고 쓰면 된다고 한다.

 

(이게 뭔 의미가 있는 행동인지 사실 잘 모르겠다)

 

=>를 이용한 함수 표현

 

매개변수가 함수임을 표현.

def functionExpression(x: (Int=>Int)) = {  }

 

자료형으로 함수 선언하기

val functionAsValue = (y:Int) => y+10

이 코드는 아래와 같이 컴파일된다.

val functionAsValue: Int => Int = new Function1[Int,Int] {

  def apply(y:Int): Int = y+10

}

//이런 표현은 또 첨보네 ... 이해가 힘들어서 눈물이 난다

 

 

함수 대입하기

 

def main() = {

  val g =  f _

  println(f(1))

}

def f(i: Int) = i

 

f 함수는 명시적으로 인수가 하나 필요하기 때문에 val g = f 하면 에러가 난다.

def f(i:Int) = i 는 하나의 함수이지 스칼라에서 말하는 함수 객체는 아니다.

 

val g가 f함수를 나타내는 Function1 객체를 가지려면 특별한 방법이 필요한 것.

 

val g = f _ 형태로 만들어서 f를 호출하는 곳에서 f를 partially applied function 형태로 만들어 주며는 partially applied function은 항상 함수 객체이기 때문에 대입이 된다 ㅎㅎ

 

혹은 f를 처음 선언할 때

def f = ( i:Int ) => i

 

이런식으로 객체화된 함수를 이용한다면 val g = f로 바로 대입도 가능하다.

 

(왜 이런 구분을 둔건지는 언어 제작자들의 그들만의 고민이 있었겟지만 이해는 안간다 ;;;)

 

 

함수 넘기기 예제

  def main(args: Array[String]):Unit = {
    val result = calc(x => x*x , 2 , 5)
    println(result)
  }
  def calc(f: Int => Int , start:Int, end:Int):Int = {
    def loop(index: Int, sum:Int): Int = {
      if(index>end) sum
      else loop(index + 1, (f(index)+sum))
    }
    loop(start,0)
  }

 

매개변수가 여러개인 상태 (미정인 상태)


  def main(args: Array[String]): Unit = {
    printlnStrings("str1", "str2", "str3")
  }

  def printlnStrings(args: String*) = {
    println(args.indices)
    println(args.length)
    for (arg <- args) {
      println(arg)
    }
  }

 

실행결과 ->

Range 0 until 3
3
str1
str2
str3

 

def main(args: Array[String]): Unit = {
    printlnStrings(1,2,3)
  }

  def printlnStrings(args: Int*) = {
    println(args.indices)
    println(args.length)
    for (arg <- args) {
      println(arg)
    }
  }

결과 ->

Range 0 until 3
3
1
2
3

 

 

이런것도 가능

 

또한 매개변수의 기본값고

 

def default(a : Int = 4 , b : Int = 5): Int = a+b

 

도 가능.

 

이럴 경우 default() 처럼 호출 가능

 


  def main(args: Array[String]): Unit = {
    f(c=1)
  }

  def f(a: Int = 4, c:Int, b:Int = 5) = {
    println(a)
    println(b) 
    println(c) 
  }

 

이런 방식도 가능.

 

apply()

 

이 메서드는 중요해서 다룬다.

apply()는 변수를 받아 함수에 적용시켜 결과를 만들어내는 설정자 같은 역할이다.

그니까 f(x) 는 내부적으로 f.apply(x) 로 해석되는 것.

 

여태까지 함수 객체를 선언했을 때도 그냥 함수 쓰듯이 쓸 수 있었던건 해당 클래스의 apply 매서드에 우리의 구현이 들어가있었기 때문일 것이다.

 

class a {

  def apply(m:Int)  = method(m)

  def method(i : Int) = i+i

}

 

def main()={

  val s = new a

  println(s(2))

}

 

결과 : 4

 

 

패턴 매칭 함수

 

스칼라에서는 switch case문을 함수로 해서 쓸 수 있다.

 

def main() = {

  println(f(100))

  println(f("hundred"))

  println(f(1000))

  println(f(1000.5))

}

def f(input: Any): Any = input match {

  case 100 => "hundred"

  case "hundred" => 100

  case etcNumber: Int => "not 100 but int" // etcNumber은 예약어가 아니라 걍 입력변수인듯

  case _ => "기타" // _는 와일드카드

}

 

결과 ->

hundred

100

not 100 but int

기타

 

+ 케이스 클래스는 패턴 매칭으로 사용 가능

 

case class Person(name:String,age:Int,rank:String)

 

main()={

  val person1 = Person("1",47,"회장")

  val person2 = Person("2",20,"직원")

  val person3 = Person("3",25,"ㅎㅎ")

 

  matchAndHi(person1)

  matchAndHi(person2)

  matchAndHi(person3)

}

def matchAndHi(person:Person):Unit = person match {

  case Person("1",47,"회장") => println("회장 ㅎㅇ")

  case Person("2",20,_) => printlnf("직원 ㅎㅇ")

  case Person("3",a,b) => printlnf(a + b)

}

 

실행결과 ->

회장 ㅎㅇ

직원 ㅎㅇ

25ㅎㅎ

 

unapply()

 

apply()가 f()에 (x)를 적용시키는 행위라면 unapply는 반대로 x를 추출하는 행위이다.

object Obj {
  def unapply(arg : String) : Option[Int] = {}
}

object Obj2 {

  def unapply(number:String) :Boolean = {

    if(number.length == 3 && number.forall(_.isDigit)) true

    else false

  }

}

 

나중에 읽어보자

https://sungjk.github.io/2017/09/25/scala-extractor.html

https://docs.scala-lang.org/ko/tutorials/tour/extractor-objects.html.html

 

extractor이라고도 하며, 주로 입력 자체가 이 class에 어울리는 출력인지를 boolean으로 반환하기도 하며 아니면 아웃풋 값을 가지고 인풋을 반환하게 Option으로도 짜는 것 같다. (Some(블라블라) 이나 None을 리턴)

 

컬랙션

 

컬랙션은 여러 데이터들이 모여있는 것. 

 

스칼라의 배열 선언

val array: Array[Int] = new Array[Int](10)

또는

val array = new Array[Int](10)

 

이렇게 하면 된다.

 

초기 값을 지정할 경우 new를 쓰지 않고 apply() 매서드를 이용해 바로 값을 집어넣어도 된다.

val array = Array(13,42,52,15,222)

 

다차원 배열

val matrix = Array.ofDim[Int](4,5) // 4x5 배열 만듦

matrix(2)(3) // 2,3 꺼냄

 

리스트

 

리스트는 앞뒤가 연결된 리스트로서 내부적으로 리스트를 붙이거나 나누는데에 효율적인 구조를 가지고 있다. 또한 동적으로 크기를 늘렸다가 줄였다가 할 수 있다.

 

val list = List()

 

List는 추상 클래스 형태 혹은 이미 완성된 객채 형태로 존재하기 때문에 보통의 클래스인 배열을 선언할 때와 달리 new를 쓰지 않는다. 즉 이미 만들어져있는 List 정적 객체의 apply()가 내부적으로 새로 동적할당한다.

 

리스트를 만드는 다른 방법으로

:: 라는 연산자는 cons라는 연산자고 마지막에 Nil이라는 연산자(NULL같은거임) 를 넣어야 한다.

 

val list = "a" :: "b" :: "c" :: Nil

 

그리고 빈 리스트를 만들려면

val list2 : List[Int] = Nil

 

그리고 concat 하는 방법은 ::: 쓰면 된다

 

val list1 = "a" :: "b" :: "c" :: Nil

val list2 = "d" :: "e" :: Nil

val list3 = list1 ::: list2 //  혹은 list1 ++ list2

 

일케 하면

abcde 가 된다. 

(List.concat() 이나 List.::: 도 가능하다. 이때는 결합되는 순서가 반대이다.)

 

Map

 

val map = Map()

val map: Map[Char,Int] = Map()

val map = Map(

  "key1" -> "value1",

  "key2" -> "value2",

  "key3" -> "value3"

)

 

map.keys / map.values 태그를 통해 뽑을 수 있다.

keys 결과

Set(key1,key2,key3)

values 결과

MapLike(value1,value2,value3)

 

 

map("key1")의 결과

value1

 

map += ("key4" -> "value4")

 

이렇게 자료를 추가 할 수도 있다.

 

map1 ++ map2 로 concat 하기도 가능.

 

집합 (Set)

 

이 친구는 중복되지 않는 값을 다룰 때 쓴다.

 

var basket: Set[String] = Set()

basket += "딸기"

basket += "포도"

basket += "딸기"

 

basket

-> Set("딸기","포도")

 

 

set은 차집합 , 합집합 연산이 있다.

 

var basket1:Set[String] = Set()
var basket2:Set[String] = Set()
basket1 += "딸기"
basket1 += "사과"
basket2 += "사과"
basket2 += "포도"
println(basket1.diff(basket2))

println(basket1 | basket2)

 

Set(딸기)
Set(딸기, 사과, 포도)

 

튜플

 

튜플은 데이터 묶음이다.

 

(a,b) 

파이썬이랑 개똑같

 

(a,b)._1

(a,b)._2

 

일케 쓰삼

 

옵션 (Option) 객체

 

이건 새로운거다.

 

옵션은 아무 값도 안 가질 수도 있고(None) 어떤 값을 가질 수도 있다 (Some())

 

옵션을 통해서 패턴 매칭을 하면 편하다.

 

값을 get 매서드와 getOrElse 매서드를 통해 가져올 수 있다.

 

val numbers = Map("one" -> 1, "two" -> 2) 
val h2 : Option[Int] = numbers.get("two") // Some(2) 리턴. 이게 옵션 객체

h2.get // 2 리턴

 

시퀀스

 

시퀀스는 인덱스에 특화된 리스트다. (리스트는 zipWithIndex 를 통해 index 기능을 같이 사용해야 한다고 한다..)

 

이터레이터

 

순회할 때 쓰는 포인터같은거. C++의 그것과 같은듯?

 

val list = List("a","b","c")

val i = list.iterator

 

while(i.hasNext){

  println(i.next)

}

 

->

a

b

c가 결과로 나온다

 

 

함수 컴비네이터

구현된 로직에 따라서 컬렉션을 변형한 수 동일 자료형의 컬렉션을 반환하는 역할을 맡는 매서드이다.

 

map() , foreach()

 

map 매서드는 f: (A) => B를 통해 함수 안의 원소들을 변형시킨 후 원본과 같은 자료형의 List를 돌려주는 역할을 한다.

 

val o = List(1,2,3,4)

val n = o.map(i => i*10)

n은 List(10,20,30,40)

o.foreach(i=>i*10)

 

filter() , filterNot()

 

cmp 함수를 넘기면 리스트에서 일부 골라서 해당되는 element만 가지고 리턴해준다.

 

partition()

 

cmp 함수를 넘기면 true인 list랑 false인 list를 나눠서 두개루 리턴한다.

 

::: , zip() , unzip()

 

val o = List(1,2,3,4)

val oo = List(5,6,7,8)

 

val n = o zip oo

val nn = o ::: oo

 

n -> List((1,5),(2,6),(3,7),(4,8))

nn-> List(1,2,3,4,5,6,7,8)

 

flatten()

 

리스트 중첩 등등을 풀어줌

 

val o = List(List(1,2,3),List(4,5))

val n = o.flatten

println(n)

 

List(1,2, 3, 4, 5)

 

foldLeft()

 

왼쪽부터 시작해서 리스트의 요소를 두개씩 살펴보며 하나로 만든다.

그리고 이 관계를 반복하여 최종적으로 하나의 값이 나오도록 짜여있다.

 

val o = List(1,2,3,4)

val n = o.foldLeft(0)((i,j) => i+j)

println(n) //결과 : 10

 

괄호안의 0은 초기값

 

 

스칼라 콘솔입력

 

import scala.io.StdIn.readLine

 

var a = readLine

var b = readLine

var c = readLine

 

Either

 

둘중에 하나라는 뜻(골때린다 ;;)

 

val result : Either[String,Int] = try{

  input.toInt

} catch {

  case e : "String"

}

 

파일입출력

 

java.io.Source

Source.formFile("파일명")

 

병렬처리

 

객체 메서드가 쭉 이어붙을때

.par

을 써주면된다

ex)

a

.par

.filter(_.length>1)

.map(_.capitalize)

.reduce(_+","+_)

 

 

 

val 선언에 lazy 키워드를 추가하면 스칼라는 lazy val 선언 우변의 평가를 우변이 처음 참조될 때까지 지연한다. 또한 평가 결과를 캐시에 담아 두고, 이후의 참조에서는 평가를 되풀이하지 않는다.

 

 

Variances (가변성)

 

class Foo[+A] // A covariant class

class Bar[-A] // A contravariant class

class Baz[A] // An invariant class

 

관련 설명

https://docs.scala-lang.org/tour/variances.html

 

class List[+A]라고 선언한다면 A를 covariant로 만들 수 있다. 이렇게 되면 만약 A가 B의 하위 타입이라면 List[A]도 List[B]의 하위 타입으로 만들어준다..

반대의 경우 (-의 경우)도 정확히 반대로 작용한다.

 

 

 

 

 

 

 

 

추가 쓰레기같이 많은 연산들;

스칼라 연산자 종류 개오바임

https://hamait.tistory.com/722