콘텐츠로 이동

2023 01 25

2023-01-25

Scala 기초 투어

TIPS

  • 스칼라에서 _는 자바의 *을 연상하면 될 듯

고차 함수

  • 개요

    • 함수를 파라미터로 받거나, 함수를 결과값으로 리턴하거나
    • 함수 자체를 First Class Value (일급 객체)
      • 함수를 ㄹㅇ 일반 값처럼 다룰 수 있어야 함
      • 함수가 일급 객체라 고차함수로 쓰기 좋음
    • 일상적인 예 map
      val salaries = Seq(20000, 70000, 40000)
      val doubleSalary = (x: Int) => x * 2
      val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)
      
    • 익명함수로 리팩터링도 가능 (심지어 괴랄하게도 가능)

      val salaries = Seq(20000, 70000, 40000)
      val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000)
      val newSalaries2 = salaries.map(_ * 2)
      

      • 어떤 타입이 파라미터로 올지 스칼라 컴파일러는 알고 있기에 _를 통해 넘겨주세요
  • 메서드를 함수로 강제 변환
    • convertCtoFx => convertCtoF(x) 로 변환
      case class WeeklyWeatherForecast(temparatures: Seq[Double]) {
        private def convertCtoF(temp: Double) = temp * 1.8 + 32
        def forecastInFahrenheit: Seq[Double] = temparatures.map(convertCtoF)
      }
      
  • 고차함수 쓰는 이유
    • 중복되는 코드를 고차함수를 통해 줄일 수 있다
      object SalaryRaiser {
        private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
          salaries.map(promotionFunction)
      
        def smallPromotion(salaries: List[Double]): List[Double] =
          promotion(salaries, salary => salary * 1.1)
      
        def greatPromotion(salaries: List[Double]): List[Double] =
          promotion(salaries, salary => salary * math.log(salary))
      
        def hugePromotion(salaries: List[Double]): List[Double] =
          promotion(salaries, salary => salary * salary)
      }
      
  • 함수를 리턴하는 함수
    def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
      val schema = if (ssl) "https://" else "http://"
      (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
    }
    
    val domainName = "www.example.com"
    def getURL = urlBuilder(ssl = true, domainName)
    val endpoint = "users"
    val query = "id=1"
    val url = getURL(endpoint, query) // "https://www.example.com/users?id=1"
    

중첩 함수

  • 개요
    • 메서드를 중첩시켜 메서드를 정의할 수 있다
      def factorial(x: Int): Int = {
        def fact(x: Int, accumulator: Int): Int = {
          if (x <= 1) accumulator
          else fact(x - 1, x * accumulator)
        }
        fact(x, 1)
      }
      
      println("Factorial of 2: " + factorial(2))
      println("Factorial of 3: " + factorial(3))
      

Multiple Parameter List

  • 개요
    • 메서드는 파라미터 파라미터 리스트를 가질 수 있음
    • Iterable trait를 봐보자

      trait Iterable[A] {
        def foldLeft[B](z: B)(op: (B, A) => B): B
      }
      

      • foldLeft는 두 파라미터 함수 op를 초기값 z에 적용
        val numbers = List(1,2,3,4,5,6,7,8,9,10)
        val res = numbers.foldLeft(0)((m, n) => m + n)
        println(res) // 55
        
  • 예시
    • 타입추론 할 수 있도록 써주세요
      def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???
      
      // 이런거 안대! 타입추론 파토남
      def notPossible = foldLeft1(numbers, 0, _ + _) 
      
      // 대신 이렇게 하세요 타입추론 할 수 있도록
      def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _)
      def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b)
      

Case Class

  • 개요
    • 불변 데이터 모델링에 유용
  • 케이스 클래스 정의
    // 정의하는 방법
    case class Book(isbn: String)
    val frankenstein = Book("987-93939212")
    
    // 예시
    case class Message(sender: String, recipient: String, body: String)
    val message1 = Message("joel610@naver.com", "luckyys610@unist.ac.kr", "hello!")
    
    println(message1.sender) // joel610@naver.com
    message1.sender = "joel610naver@gmail.com" // 안대! 내부적으로 val 쓰거든
    
  • 비교
    • 케이스 클래스 인스턴스는 레퍼런스로 비교 안해
    • 구조 그 자체로 비교됨
      case class Message(sender: String, recipient: String, body: String)
      val message1 = Message("joel610@naver.com", "luckyys610@unist.ac.kr", "hello!")
      val message2 = Message("joel610@naver.com", "luckyys610@unist.ac.kr", "hello!")
      val result = message1 == message2 // true
      
  • 복제
    • copy 메서드를 통해서는 안의 인스턴스 변수 얕은 복사가 됨
      case class Message(sender: String, recipient: String, body: String)
      val message1 = Message("joel610@naver.com", "luckyys610@unist.ac.kr", "hello!")
      val message2 = message1.copy(sender = message1.recipient, recipient = "joel.jo@kakaocorp.com")
      

패턴 매칭

  • 개요
    • 자바의 switch와 같아
    • if-else 대신 써볼래요?
  • 문법
    import scala.util.Random
    
    val x: Int = Random.nextInt(10)
    
    x match {
      case 0 => "zero"
      case 1 => "one"
      case 2 => "two"
      case _ => "other"
    }
    
  • 활용
    sealed trait Notification
    case class Email(sender: String, title: String, body: String) extends Notification
    case class SMS(caller: String, message: String) extends Notification
    case class VoiceRecording(contactName: String, link: String) extends Notification
    
    def showNotification(notification: Notification): String = {
      notification match {
        case Email(sender, title, _) => 
            s"You got an email from $sender with title: $title"
        case SMS(number, message) => 
            s"You got an SMS from $number! Message: $message"
        case VoiceRecording(name, link) =>
            s"You received a Voice Recording from $name! Click the link to hear it: $link"
      }
    }
    val someSms = SMS("12345", "Are you there?")
    val someVoiceRecording = VoiceRecording("Tom", "recording.org/id/123")
    
    println(showNotification(someSms))
    println(showNotification(someVoiceRecording))
    
  • 패턴 가드
    • boolean expresion을 통해 조건을 걸어 패턴 가드를 걸 수 있다
      def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
        notification match {
          case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
            "You got an email from special someone!"
          case SMS(number, _) if importantPeopleInfo.contains(number) => 
            "You got an SMS from special someone!"
          case other => 
            showNotification(other)
        }
      }
      
      val importantPeopleInfo = Seq("123-456", "joel610@naver.com")
      val importantSms = SMS("123-456", "hi!")
      val importantEmail = Email("joel610@naver.com", "Hello!", "hi!")
      
      println(showImportantNotification(importantSms, importantPeopleInfo))
      println(showImportantNotification(importantEmail, importantPeopleInfo))
      
  • 타입만 가지고도 휘뚜루 마뚜루
    • 타입으로만 매칭 시켜서 액션시킬 수 있음
      sealed trait Device
      case class Phone(model: String) extends Device {
        def screenOff = "Turning screen Off"
      }
      
      case class Computer(model: String) extends Device {
        def screenSaverOn = "Turning screen saver on"
      }
      
      def goIdle(device: Device): String = device match {
        case p: Phone => p.screenOff
        case c: Computer => c.screenSaverOn
      }
      
  • Sealed Types
    • sealed 키워드가 있다면, 해당 trait/abstract class의 자식은 같은 파일내에서만 정의해야해!
    • 이러면 match 같은거 쓸 때 다른 곳에 자식없다는게 명확해지니 개꿀이겠지?

싱글턴 오브젝트

  • 개요
    • 최초 참조시에 lazy init
    • 딱 하나만 만들어짐
    • Java의 static이랑 느낌이 비슷
  • 문법
    • object 키워드
      package logging
      
      object Logger {
        def info(message: String): Unit = println(s"INFO: $message")
      }
      
      import logging.Logger.info
      
      class Project(name: String, daysToComplete: Int)
      
      class Test {
        val project1 = new Project("TPS Reports", 1)
        val project2 = new Project("Website Redesign", 5)
        info("Created Projects")
      }
      
  • Companion objects

    • 클래스와 같은 이름의 오브젝트(싱글턴)을 companion object 라고 부름
    • 오브젝트와 같은 이름의 클래스를 companion class 라고 부름
      import scala.math.pow
      
      case class Circle(radius: Double) {
        import Circle._
        def area: Double = calculateArea(radius)
      }
      
      object Circle {
        private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0)
      }
      
      val circle1 = Circle(5.0)
      circle1.area
      
    • companion object에 factory method를 넣어둘 수도 있어
      class Email(val username: String, val domainName: String)
      
      object Email {
        def fromString(emailString: String): Option[Email] = {
          emailString.split('@') match {
            case Array(a, b) => Some(new Email(a, b))
            case _ => None
          }
        }
      }
      
      val scalaCenterEmail = Email.fromString("scala.center@epfl.ch")
      scalaCenterEmail match {
        case Some(email) => println(email.username, email.domainName)
        case None => println("Fail!")
      }
      

REGEX

  • 개요
    • .r 하나면 모든 String이 Regex로 변경된다
  • 활용
    import scala.util.matching.Regex
    
    val numberPattern: Regex = "[0-9]".r 
    
    numberPattern.findFirstMatchIn("awesomepassword") match {
      case Some(_) => println("Password OK!")
      case None => println("Password must contain a number")
    }
    

함수형 프로그래밍에서의 커링