• 사이드 이펙트 : 함수형은 input, output 이런 것을 할 수 있는데 그 이외에 할 수 있는 것들 (중간에 쓰윽-하는 것들)
    • 안 쓸 수 있으면 안쓰는 것이 좋음. 컴파일러가 체크 불가능
    • 그러나 신경은 써야 한다!
    • 하스켈에서는 사이드 이펙트가 허용되지 않는 편!

Generic

  • 자바의 Generic과 의미가 다를 수 있음
  • Trait보다 더 일반화된 것

In [4]:
final case class Box[A](value: A) // type이 A라고 정해짐, A가 잘 정의되면 어떤 것이든 담을 수 있음


Out[4]:
defined class Box

In [5]:
Box(0)
// 알아서 타입 추론을 해서 맞춰줌


Out[5]:
res4: Box[Int] = Box(0)

In [6]:
Box("foo")


Out[6]:
res5: Box[String] = Box("foo")

Generic Method


In [7]:
def generic[A](in: A): A = in


Out[7]:
defined function generic

In [8]:
generic[String]("foo")


Out[8]:
res7: String = "foo"

In [9]:
generic("foo")


Out[9]:
res8: String = "foo"

In [10]:
generic(1)


Out[10]:
res9: Int = 1

Syntax

  • class

    • (case) class Name[A, B, ...](...){....}
    • 자바는 T를 많이 쓰는데 스칼라에선 A, B를 씀
  • Trait

    • trait Name[A, B, ...]{..}
  • Method

    • def name[A,B ...](...){...}

EXERCISE: GENERIC LIST

  • 다음 IntList를 Generic을 써서 일반적인 타입의 데이터를 담을 수 있도록 수정한 LinkedList 타입을 작성해 보세요.

In [23]:
sealed trait LinkedList[A]
final case class End[A]() extends LinkedList[A]
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]


Out[23]:
defined trait LinkedList
defined class End
defined class Pair

In [24]:
val intList = Pair(1, Pair(2, Pair(3, End())))


Out[24]:
intList: Pair[Int] = Pair(1,Pair(2,Pair(3,End())))

In [25]:
val strList = Pair("a", Pair("b", Pair("c", End())))


Out[25]:
strList: Pair[String] = Pair(a,Pair(b,Pair(c,End())))

In [ ]:

Functions

  • Function TYPE

    • (A, B, ...) => C
    • 만약 파라미터가 한개라면
    • A => B
  • (parameter: type, ..) => expression


In [21]:
val sayHi = () => "Hi!"
// unit을 받아서 string을 토하는 함수
// lambda : 익명 함수


Out[21]:
sayHi: () => String = <function0>

In [22]:
sayHi()


Out[22]:
res21: String = "Hi!"

In [26]:
val add1 = (x: Int) => x + 1


Out[26]:
add1: Int => Int = <function1>

In [27]:
add1(10)


Out[27]:
res26: Int = 11

In [27]:
add1("a")


cmd27.sc:1: type mismatch;
 found   : String("a")
 required: Int
val res27 = add1("a")
                 ^
Compilation Failed

In [28]:
val sum = (x: Int, y: Int) => x + y


Out[28]:
sum: (Int, Int) => Int = <function2>

In [29]:
sum(1, 2)


Out[29]:
res28: Int = 3
  • 함수형 프로그래밍에서는 method에서 함수를 인자로 받을 수 있음!!

In [30]:
def oneAndTwo[A](f: (Int, Int) => A): A = f(1,2)
// 메서드에서 함수를 인자로 받음


Out[30]:
defined function oneAndTwo

In [31]:
oneAndTwo((x, y) => x + y)
// 여기선 type을 안적어도 됨 (위에서 Int로 정의함)


Out[31]:
res30: Int = 3

In [32]:
oneAndTwo((x, y) => s"($x, $y)")


Out[32]:
res31: String = "(1, 2)"

Placeholder syntax

  • 함수를 간단히 쓸 수 있는 Syntax

In [33]:
(_: Int) + 1 // (x: Int) => x + 1
// x를 한번만 쓰이니까 굳이 쓸 필요가 뭐있나! 이런 느낌


Out[33]:
res32: Int => Int = <function1>

In [34]:
res32(1)


Out[34]:
res33: Int = 2

In [34]:
// 파라미터 타입을 추론할 수 있는 경우엔 타입을 생략할 수 있습니다

_ + _ // (a, b) => a + b
foo(_) // (a) => foo(a)
foo(_, b) // (a) => foo(a, b)
_(foo) // (a) => a(foo)

In [35]:
case class Pair[A, B](a: A, b: B) {
    def calc[C](f: (A, B) => C): C = f(a, b)
}


Out[35]:
defined class Pair

In [36]:
Pair(1, 2).calc(_+_)
// 길게 쓰면 아래와 같이 씀!


Out[36]:
res35: Int = 3

In [39]:
val caseInt = Pair(1, 2).calc((a, b) => a+b)


Out[39]:
caseInt: Int = 3

In [37]:
Pair("foo", "bar").calc(_+_)


Out[37]:
res36: String = "foobar"

In [39]:
// 짧아지긴 하지만.. 아직 적응이 잘 안될거에요.. 계속 하다보면..!

Methods to functions


In [40]:
object Adder {
    def add1(n: Int): Int = n+1
}


Out[40]:
defined object Adder

In [40]:
Adder.add1
// error 발생


cmd40.sc:1: missing argument list for method add1 in object Adder
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `add1 _` or `add1(_)` instead of `add1`.
val res40 = Adder.add1
                  ^
Compilation Failed

In [41]:
Adder.add1 _
// 함수로 바꿔줌


Out[41]:
res40: Int => Int = <function1>

In [42]:
Adder.add1(_)
// 함수로 바꿔줌


Out[42]:
res41: Int => Int = <function1>

In [43]:
case class Box[A](a: A) {
    def modify[B](f: A => B): B = f(a)
}


Out[43]:
defined class Box

In [44]:
Box(42).modify(Adder.add1)


Out[44]:
res43: Int = 43

In [45]:
Box(42) modify Adder.add1


Out[45]:
res44: Int = 43

In [45]:
Box("foo") modify Adder.add1
// add1은 Int를 받는 함수인데 foo는 string이라 error 발생


cmd45.sc:1: type mismatch;
 found   : Int => Int
 required: String => ?
val res45 = Box("foo") modify Adder.add1
                                    ^
Compilation Failed

CURRING

  • 괄호가 여러개 쌓여있는 식으로 method를 만들 수 있음

In [46]:
def curring(x: Int)(y: Int): Int = x + y


Out[46]:
defined function curring

In [47]:
curring(1)(2)


Out[47]:
res46: Int = 3

왜 이렇게 쓰느냐?

  • 하나에서 타입을 유추한 후, 그것을 다시 사용해야 하는 경우 커링을 써야 합니다!

In [48]:
def generic[A, B](a: A, f: A => B): B = f(a)


Out[48]:
defined function generic

In [48]:
generic(1, _ + 1)
// 왜 컴파일이 안될까요?
// 파라미터 타입이 없음..!


cmd48.sc:1: missing parameter type for expanded function ((x$1) => x$1.$plus(1))
val res48 = generic(1, _ + 1)
                       ^
Compilation Failed

In [48]:
generic(1, a=> a + 1)
// 타입 추론은 괄호 단위로만 가능. 
// a:A를 유추해 f:A를 쓸 수 없습니다. 명시적으로 적어줘야 함


cmd48.sc:1: missing parameter type
val res48 = generic(1, a=> a + 1)
                       ^
Compilation Failed

In [50]:
def genericCurring[A, B](a: A)(f: A => B): B = f(a)


Out[50]:
defined function genericCurring

In [51]:
genericCurring(1)(_ + 1)


Out[51]:
res50: Int = 2

EXERCISE: PAIRS

  • Pair 를 구현해보세요. 다음이 성립하도록 해야 합니다.

In [54]:
val pair1 = Pair("foo", 1)
val pair2 = Pair(2, "bar")
assert(pair1.one == "foo")
assert(pair1.two == 1)
assert(pair2.one == 2)
assert(pair2.two == "bar")

println("ok")


ok
Out[54]:
pair1: Pair[String, Int] = Pair("foo", 1)
pair2: Pair[Int, String] = Pair(2, "bar")

In [52]:
case class Pair[A, B](one: A, two: B)


Out[52]:
defined class Pair
  • 프로그래밍을 해보셨던 분들이 함수형 프로그래밍을 힘들어하는 경우가 많습니다,,,,,,,
  • 차라리 처음 하시는 분이 더 수월할 듯

Tuple

  • Pair를 더 많은 원소를 가질 수 있도록 일반화한 Scala의 built-in 타입
  • Tuple1[A], Tuple2[A, B], ..., Tuple22[A, B, C, ...]
  • (A, B, C, ...)의 형태로도 사용가능 (syntactic sugar)

In [55]:
Tuple2("foo", 1)


Out[55]:
res54: (String, Int) = ("foo", 1)

In [56]:
("foo", 1)


Out[56]:
res55: (String, Int) = ("foo", 1)

In [57]:
("foo", 1, true)


Out[57]:
res56: (String, Int, Boolean) = ("foo", 1, true)

패턴 매칭


In [58]:
("foo", 1) match { case (a, b) => a + b}


Out[58]:
res57: String = "foo1"

원소 접근


In [59]:
val x = (1, "b", true)


Out[59]:
x: (Int, String, Boolean) = (1, "b", true)

In [60]:
x._1


Out[60]:
res59: Int = 1

In [61]:
x._3


Out[61]:
res60: Boolean = true

EXERCISE: GENERIC SUM

  • 두 개의 서브타입 First와 Second를 가지는 Sum타입을 구현해보세요. 다음 테스트가 통과되도록 하셔야 합니다.

In [64]:
sealed trait Sum[A, B]
final case class First[A, B]() extends Sum[A, B]
final case class Second[A, B]() extends Sum[A, B]


Out[64]:
defined trait Sum
defined class First
defined class Second

In [62]:
assert(First[Int, String](1).value == 1)
assert(Second[Int, String]("foo").value == "foo")
val sum: Sum[Int, String] = Second("foo")
val matched: String = sum match {
  case First(x) => x.toString
  case Second(x) => x
}
assert(matched == "foo")
println("ok")


cmd62.sc:1: too many arguments for method apply: ()cmd62Wrapper.this.cmd61.wrapper.First[Int,String] in object First
val res62_0 = assert(First[Int, String](1).value == 1)
                                       ^cmd62.sc:2: too many arguments for method apply: ()cmd62Wrapper.this.cmd61.wrapper.Second[Int,String] in object Second
val res62_1 = assert(Second[Int, String]("foo").value == "foo")
                                        ^cmd62.sc:3: too many arguments for method apply: ()cmd62Wrapper.this.cmd61.wrapper.Second[A,B] in object Second
val sum: Sum[Int, String] = Second("foo")
                                  ^cmd62.sc:5: wrong number of arguments for pattern cmd62Wrapper.this.cmd61.wrapper.First[Int,String]()
  case First(x) => x.toString
            ^cmd62.sc:6: wrong number of arguments for pattern cmd62Wrapper.this.cmd61.wrapper.Second[Int,String]()
  case Second(x) => x
             ^
Compilation Failed


In [68]:
sealed trait Sum[A, B]
final case class First[A, B](value: A) extends Sum[A, B]
final case class Second[A, B](value: B) extends Sum[A, B]


Out[68]:
defined trait Sum
defined class First
defined class Second

In [ ]:
// 인자를 1개만 받음
// .value가 써있기 때문에 value를 넣어주면 됨

In [69]:
assert(First[Int, String](1).value == 1)
assert(Second[Int, String]("foo").value == "foo")
val sum: Sum[Int, String] = Second("foo") 
val matched: String = sum match {
  case First(x) => x.toString
  case Second(x) => x
}
assert(matched == "foo")
println("ok")


ok
Out[69]:
sum: Sum[Int, String] = Second(foo)
matched: String = "foo"

EXERCISE: MAYBE

  • 두 개의 서브타입 Just와 Empty를 가지는 Maybe타입을 구현해보세요. 다음 테스트가 통과되도록 하셔야 합니다.

In [71]:
def divide(a: Int, b: Int): Maybe[Int] = b match {
  case 0 => Empty()
  case _ => Just(a/b)
}

assert(divide(10, 2) == Just(5))
assert(divide(10, 0) == Empty())


cmd71.sc:6: too many arguments for method apply: ()cmd71Wrapper.this.cmd70.wrapper.Just[A] in object Just
val res71_1 = assert(divide(10, 2) == Just(5))
                                          ^cmd71.sc:3: too many arguments for method apply: ()cmd71Wrapper.this.cmd70.wrapper.Just[A] in object Just
  case _ => Just(a/b)
                ^
Compilation Failed


In [73]:
sealed trait Maybe[A]
final case class Empty[A]() extends Maybe[A]
final case class Just[A](value: A) extends Maybe[A]


Out[73]:
defined trait Maybe
defined class Empty
defined class Just

In [74]:
def divide(a: Int, b: Int): Maybe[Int] = b match {
  case 0 => Empty()
  case _ => Just(a/b)
}

assert(divide(10, 2) == Just(5))
assert(divide(10, 0) == Empty())


Out[74]:
defined function divide

In [76]:
divide(10,1)


Out[76]:
res75: Maybe[Int] = Just(10)

In [ ]:

EXERCISE: MAP

  • 앞서 작성했던 LinkedList에 map메소드를 구현해보세요.

  • 타입 시그니쳐는 다음과 같습니다.


In [ ]:
sealed trait LinkedList[A]{
    def map[B](f: A => B): LinkedList[B] =  
}
final case class End[A]() extends LinkedList[A]
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]

In [76]:
val l = Pair(1, Pair(2, Pair(3, End())))
assert(l.map(_ + 1) == Pair(2, Pair(3, Pair(4, End()))))

println("ok")


cmd76.sc:2: value map is not a member of cmd76Wrapper.this.cmd51.wrapper.Pair[Int,cmd76Wrapper.this.cmd51.wrapper.Pair[Int,cmd76Wrapper.this.cmd51.wrapper.Pair[Int,cmd76Wrapper.this.cmd22.wrapper.End[Nothing]]]]
val res76_1 = assert(l.map(_ + 1) == Pair(2, Pair(3, Pair(4, End()))))
                       ^
Compilation Failed


In [77]:
sealed trait LinkedList[A]{
    def map[B](f: A => B): LinkedList[B] = this match {
        case End() => End[B]()
        case Pair(head, tail) => Pair[B](f(head) , tail.map(f))
    } 
}
final case class End[A]() extends LinkedList[A]
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]


Out[77]:
defined trait LinkedList
defined class End
defined class Pair

In [78]:
val l = Pair(1, Pair(2, Pair(3, End())))
assert(l.map(_ + 1) == Pair(2, Pair(3, Pair(4, End()))))

println("ok")


ok
Out[78]:
l: Pair[Int] = Pair(1,Pair(2,Pair(3,End())))

EXERCISE: FOLD

  • LinkedList에 fold메소드를 구현해보세요.

  • 타입 시그니쳐는 다음과 같습니다


In [ ]:
def fold[B](init: B)(f: (A, B) => B): B = ???

In [ ]:
// test
val list0 = Pair(1, Pair(2, Pair(3, Pair(4, End))))
assert(list0.fold(0)(_ + _) == 10)
val list1 = Pair(1, Pair(2, Pair(3, Pair(4, End))))
assert(list1.fold(1)(_ * _) == 24)


In [79]:
sealed trait LinkedList[A]{
    def fold[B](init: B)(f: (A, B) => B): B = this match{
        case End() => init
        case Pair(head, tail) => f(head, tail.fold(init)(f))
    }
}
final case class End[A]() extends LinkedList[A]
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]


Out[79]:
defined trait LinkedList
defined class End
defined class Pair

EXERCISE: FLATMAP

  • LinkedList에 flatMap메소드를 구현해보세요.
  • 하나를 여러개로 만드는 것
  • 타입 시그니쳐

In [ ]:
// test
val list = Pair(1, Pair(2, Pair(3, Pair(4, End))))
val filtered = list.flatMap{
  case n if x % 2 == 0 => End
  case n => Pair(n, End)
}
assert(filtered == Pair(1, Pair(3, End)))


In [80]:
sealed trait LinkedList[A] {
  def map[B](f: A => B): LinkedList[B] = this match {
    case End() => End[B]()
    case Pair(head, tail) => Pair[B](f(head), tail.map(f))
  }
  def fold[B](init: B)(f: (A, B) => B): B = this match {
    case End() => init
    case Pair(head, tail) => f(head, tail.fold(init)(f))
  }
  def flatMap[B](f: A => LinkedList[B]): LinkedList[B] = this match {
    case End() => End()
    case Pair(head, tail) =>
      val headList: LinkedList[B] = f(head)
      val tailList: LinkedList[B] = tail.flatMap(f)
      headList ++ tailList
  }
  def ++(that: LinkedList[A]): LinkedList[A] = this match {
    case End() => that
    case Pair(head, tail) => Pair(head, tail ++ that)
  }
}
final case class End[A]() extends LinkedList[A]
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]
val list = Pair(1, Pair(2, Pair(3, Pair(4, End()))))
val filtered = list.flatMap[Int]{
  case n if n % 2 == 0 => End()
  case n => Pair(n, End())
}
assert(filtered == Pair(1, Pair(3, End())))

println("Ok!")


Ok!
Out[80]:
defined trait LinkedList
defined class End
defined class Pair
list: wrapper.wrapper.Pair[Int] = Pair(1,Pair(2,Pair(3,Pair(4,End()))))
filtered: wrapper.wrapper.LinkedList[Int] = Pair(1,Pair(3,End()))

VARIANCE (주의: 어려움)

  • variance에 대해 얼핏 알고.. 디버깅하는 노하우를 알려드리겠습니다

In [81]:
sealed trait Maybe[A]
final case class Just[A](value: A) extends Maybe[A]
final case class Empty[A]() extends Maybe[A]


Out[81]:
defined trait Maybe
defined class Just
defined class Empty

In [81]:
// 인자가 없으니 아래처럼 만들면 간단한데!
sealed trait Maybe[A]
final case class Just[A](value: A) extends Maybe[A]
final case object Empty extends Maybe[???]


cmd81.sc:3: not found: type ???
final case object Empty extends Maybe[???]
                                      ^
Compilation Failed

In [82]:
sealed trait Maybe[A]
final case class Just[A](value: A) extends Maybe[A]
final case object Empty extends Maybe[Nothing]
// Nothing : 어떤 것들의 sub type!!!


Out[82]:
defined trait Maybe
defined class Just
defined object Empty

In [82]:
// 명시적으로 해주는 것을 variance

In [82]:
val perhaps: Maybe[Int] = Empty


cmd82.sc:1: type mismatch;
 found   : cmd82Wrapper.this.cmd81.wrapper.Empty.type
 required: cmd82Wrapper.this.cmd81.wrapper.Maybe[Int]
Note: Nothing <: Int (and cmd82Wrapper.this.cmd81.wrapper.Empty.type <: cmd82Wrapper.this.cmd81.wrapper.Maybe[Nothing]), but trait Maybe is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
val perhaps: Maybe[Int] = Empty
                          ^
Compilation Failed

In [83]:
sealed trait Maybe[+A]
final case class Just[A](value: A) extends Maybe[A]
final case object Empty extends Maybe[Nothing]


Out[83]:
defined trait Maybe
defined class Just
defined object Empty

  • 왠만하면 covariant일 것!
  • 함수의 인자로 타입을 쓰는 경우엔 contravariant도 있음

In [ ]:
trait Function0[+R] {
  def apply: R
}

trait Function1[-A, +B] {
  def apply(a: A): B
}

trait Function2[-A, -B, +C] {
  def apply(a: A, b: B): C
}

In [ ]:
case class Box[A](a: A) {
  def map[B](f: Function1[A, B]): Box[B] = Box(f(a))
}

CONTRAVARIANT POSITION


In [83]:
case class Box[+A](a: A) {
  def set(a: A): Box[A] = Box(a) // 컴파일 안됨! 인자에만 들어갈 수 있음
}


cmd83.sc:2: covariant type A occurs in contravariant position in type A of value a
  def set(a: A): Box[A] = Box(a) // 컴파일 안됨!
          ^
Compilation Failed

In [ ]:
case class Box[+A](a: A) {
  def set[AA >: A](a: AA): Box[AA] = Box(a) 
}
// AA라는 super type이 있을 경우 AA를 넣어서 Box type을 만든다^^.. 이해 안가죠?

Type Bound

  • A <: B : A is a subtype of B
  • A >: B : A is a supertype of B

In [83]:
sealed trait Sum[+A, +B] {
  def flatMap[C](f: B => Sum[A, C]): Sum[A, C] = this match {
    case Failure(v) => Failure(v)
    case Success(v) => f(v)
  }
}
final case class Failure[A](value: A) extends Sum[A, Nothing]
final case class Success[B](value: B) extends Sum[Nothing, B]


cmd83.sc:2: covariant type A occurs in contravariant position in type B => Helper.this.Sum[A,C] of value f
  def flatMap[C](f: B => Sum[A, C]): Sum[A, C] = this match {
                 ^
Compilation Failed
  • 아 variance 잘못 썼구나! 아래처럼 수정..

In [84]:
sealed trait Sum[+A, +B] {
  def flatMap[AA >: A, C](f: B => Sum[AA, C]): Sum[AA, C] = this match {
    case Failure(v) => Failure(v)
    case Success(v) => f(v)
  }
}
final case class Failure[A](value: A) extends Sum[A, Nothing]
final case class Success[B](value: B) extends Sum[Nothing, B]


Out[84]:
defined trait Sum
defined class Failure
defined class Success

EXERCISE: CALCULATOR

  • 다음에서 eval 메소드를 구현해보세요
  • Generic sub type
  • Tuple 활용
  • 숙제

In [ ]:
sealed trait Expression {
  def eval: Sum[String, Double] = ???
}
final case class Addition(left: Expression, right: Expression) extends Expression
final case class Subtraction(left: Expression, right: Expression) extends Expression
final case class Division(left: Expression, right: Expression) extends Expression
final case class SquareRoot(value: Expression) extends Expression
final case class Number(value: Double) extends Expression

In [ ]:
// test
assert(Addition(Number(1), Number(2)).eval == Success(3))
assert(SquareRoot(Number(-1)).eval == Failure("Square root of negative number"))
assert(Division(Number(4), Number(0)).eval == Failure("Division by zero"))
assert(Division(Addition(Subtraction(Number(8), Number(6)), Number(2)), Number(2)).eval == Success(2.0))

In [ ]:


In [ ]:


In [ ]:


In [ ]: