콘텐츠로 이동

2023 03 02

2023-03-02

Scala at Light Speed

참고: https://www.youtube.com/watch?v=-8V6bMjThNo&list=PLmtsMNDRU0BxryRX4wiwrTZ661xcp6VPM

Functional Programming

  • 함수형 프로그래밍
    • 함수 자체를 합성할 수 있어야하고, (체이닝이 가능하다는 뜻 같음)
    • 함수 자체를 argument로 넘길 수 있어야 하고
    • 함수 자체를 결과값으로 반환할 수 있어야 해
  • 다만 스칼라는 JVM위에서 돌아가는 언어
    • 스칼라는 OOP이고, JVM위에서 돌아가다보니까, 함수형으로 휘뚜루마뚜루 쓰려면 약간의 트릭이 필요해
    • 따라서 스칼라는 Function_X 타입의 trait를 인스턴스화해서 사용하는 방식임
    • 자바8의 함수형 인터페이스와 느낌이 비슷 (아마 자바가 이거에 영향 받지 않았을까 싶음)
    • Function_X 타입의 trait는 apply() 함수를 오버라이딩 해줘야 하는데...
    • 여기에서 apply() 된 친구들은 메서드명을 생략할 수 있음
      val simpleIncrementer = new Function1[Int, Int] {
        override def apply(arg: Int): Int = arg + 1
      }
      
      simpleIncrementer.apply(23)
      simpleIncrementer(23)
      
  • Syntax Sugar
    • 다만 매번 new Function1... 하면서 구구절절 인스턴스화 시키는거 귀찮았겠지?
    • 자바의 람다함수처럼 이걸 좀 바꿔보자고 이렇게
      val doubler1 = new Function1[Int, Int] = {
        override def apply(arg: Int): Int = arg * 2
      }
      
      val doubler2: Function1[Int, Int] = (x: Int) => 2 * x
      
      val doubler3: Int => Int = (x: Int) => 2 * x
      
  • 고차함수
    • 함수 자체를 argument로 넘기거나, 반환값으로 돌려주거나
    • java8의 스트림과 유사
    • 다만 (x => x + 1) 이런 작업 귀찮으시다며 (_ + 1)로 변경하심
      val aMappedList = List(1, 2, 3).map(_ + 1)
      val aFlatMappedList = List(1, 2, 3).flatMap(x => List(x, 2 * x))
      // alternative-syntax
      val aFlatMappedList2 = List(1, 2, 3).flatMap { x => 
        List(x, 2 * x)
      }
      
  • For comprehensions
    • flatmap + map 조합과 유사하게 사용할 수 있는 문법
      val pairs = for {
        number <- List(1, 2, 3)
        letter <- List('a', 'b', 'c')
      } yield s"$number-$letter"
      
  • 스칼라 Collections

    • [List]
      val aList = List(1, 2, 3, 4, 5)
      val firstElem = aList.head // 1
      val rest = aList.tail // (2, 3, 4, 5)
      val aPrependList = 0 :: aList // (0, 1, 2, 3, 4, 5)
      val anExtendedList = 0 +: aList :+ 6 // (0, 1, 2, 3, 4, 5, 6)
      
    • [Sequence]
      val aSequence: Seq[Int] = Seq(1, 2, 3)
      val getIndexOne = aSequence(1) // .apply(1) 과 동치 - 결과값 2
      
    • [Vector]
      • Sequence를 빠르게 접근할 수 있는 자료구조
      • 아마 내부적으로 배열을 쓰는 것 같긴함.. (확실치 않음)
    • [Set]
      • 중복 멈춰!
        val aSet = Set(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
        
    • [Range]
      • val aRange = 1 to 1000
    • [Tuple]
      • 순서가 있으며, 리스트는 같은 타입의 친구들을 담지만, 튜플은 다른 타입의 친구들도 쓱 담을 수 있음
        val myTuple = ("hello", 123, true)
        println(myTuple._1)
        println(myTuple._2)
        println(myTuple._3)
        
    • [Map]
      val phoneBook: Map[String, Int] = Map(
        "joel" -> 1234,
        "jo" -> 2345
      )
      

Pattern Matching

  • 패턴 매칭 문법
    • match, case 등을 통해 해당 변수가 어떠한 case와 매칭이 되는지를 검사
    • _ 는 any() 정도로 생각해도 무방
    • 각각의 case는 expression으로 구성이 되어 있음. 특정 함수라고 생각해도 좋음
      val anInteger = 55
      val order = anInteger match {
        case 1 => "first"
        case 2 => "second"
        case 3 => "third"
        case _ => anInteger + "th"
      }
      
  • Case Class Decomposition
    • Case Class는...
      • 값이 같다면 equals & hashcode가 동일하도록 하는 클래스 (VO)
      • apply 함수 기본적으로 가지고 있어 함수형 맹키로다가 new 인스턴스화 지원
      • 직렬화하는 과정에서 많이 쓰이면서도
      • 패턴매칭에도 많이 쓰임
    • 패턴 매칭에서 그냥 자료형 추론해서 쓰이는 것 같기도 하고, 특정하게 하나의 값으로 박아버리는 것도 가능해보임
      • anyType(Int.class) || any() || "value"
        case class Person(name: String, age: Int)
        val joel = Person("Joel", 25)
        val joelDetector = joel match {
          case Person(name, age) => s"I am $name and I am $age years old"
          case _ => "sth else"
        }
        println(joelDetector) // I am Joel and I am 25 years old
        
        val joelDetectorFailed = joel match {
          case Person("Joel.Jo", _) => "I am Joel"
          case _ => "sth else"
        }
        println(joelDetectorFailed) // sth else
        
  • Tuple/List Deconstruction
    • 튜플/리스트도 하나하나 매칭시킬 수 있음
      val fujiiKaze = ("Fujii Kaze", "Japanese")
      val artistDescription = fujiiKaze match {
        case (name, nationality) => s"$name is $nationality"
        case _ => "Nothing"
      }
      
      val aList = List(1, 2, 3)
      val listDescription = aList match {
        case List(_, 2, _) => "List containing 2 on its second position"
        case _ => "unknown"
      }
      
  • TIPS
    • Best Practice로는 마지막에 case _ =>를 써주자. 그래야 알맞은 패턴이 없습니다 라며 에러를 던지는 것을 방지할 수 있다.