๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐Ÿ‘‰ Functional Programming Conference LiftIO 2021

๐ŸŽˆ ์—ฐ์†๋œ ์šฐ์—ฐ์œผ๋กœ ๊พธ๋ ค์ง„ ๊ฐœ๋ฐœํŒ€์˜ ํ•จ์ˆ˜ํ˜• Scala ํ™œ์šฉ๊ธฐโ€‹

Github Repoโ€‹

package purecoincidence

object HigherLevel extends App {
def factA(n: Int): Int = {
var r = 1
for {
i <- 1 to n
} r *= i
r
}

def factB(n: Int): Int =
if (n <= 1) 1
else n * factB(n - 1)

def factC(n: Int): Int =
(1 to n).product

println((
factA(5),
factB(5),
factC(5)
)) // => (120,120,120)
}

FP ํŠน์ง•โ€‹

  • ๊ธฐ๊ณ„ ์ค‘์‹ฌ ๊ด€์ ๊ณผ ์‚ฌ๋žŒ ์ค‘์‹ฌ ๊ด€์ ์˜ ์ฐจ์ด
  • ์ž‘์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ•ฉ์„ฑํ•ด ํฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์‰ฝ๋‹ค.
  • ์ƒํƒœ๋‚˜ ๋ถ€์ˆ˜ํšจ๊ณผ์—†์ด ์ˆœ์ˆ˜ ํ•จ์ˆ˜์˜ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ๋งŒ์„ ๋‹ค๋ฃฌ๋‹ค๋ฉด, ๊ทธ๋งŒํผ ์ถ”๋ก ์ด๋‚˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฝ๋‹ค.
  • ์‹œ๊ฐ„๊ณผ ์ƒํƒœ๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์ฒ˜๋ฆฌ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ง„๋‹ค.

๊ณ ์ฐจํ•จ์ˆ˜โ€‹

  • ์–ด๋–ค ํ•จ์ˆ˜๊ฐ€, ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๊ฑฐ๋‚˜, ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋กœ ๋ณดํ†ต์˜ ๊ฐ’์ด ์•„๋‹Œ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ๋˜๋Š”, ๊ทธ ๋‘˜ ๋‹ค๋ฅผ ํ•œ๋‹ค.
  • ํ•จ์ˆ˜๋ฅผ ํ‰๋ฒ•ํ•œ ๊ฐ’์œผ๋กœ ์ทจ๊ธ‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ๊ฐ€๋Šฅ.
package purecoincidence

object HigherOrderFunction {
def multiple(n: Int): Int => Int =
x => x * n

def pow(r: Int): Int => Int =
x => List.fill(r)(x).product

val triple: Int => Int = multiple(3)
val square: Int => Int = pow(2)

def main(args: Array[String]) =
println((
List(1, 2, 3, 4).map(triple compose square),
List(1, 2, 3, 4).map(square).map(triple)
)) // => List(3, 12, 27, 48), ...
}

์ˆœ์ˆ˜ ํ•จ์ˆ˜โ€‹

๊ฐ™์€ ์ธ์ˆ˜๋ฅผ ๋„˜๊ฒจ ํ˜ธ์ถœํ•˜๋ฉด, ์–ธ์ œ๋‚˜ ๊ฒฐ๊ณผ๊ฐ’์ด ๊ฐ™๊ฒŒ ๋‚˜์˜จ๋‹ค.
ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด๋„, ๋ณ„๋„์˜ ๋ถ€์ˆ˜ ํšจ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • Impure
// Impure ํ•˜๋‹ค.
package purecoincidence

object Impurity extends App {
var current: Int = 0
def impure(n: Int): Int = {
current += n
current
}

val i = impure(2)

println((
i + i,
impure(2) + i,
impure(2) + impure(2)
)) // (4,6,14)
}
  • pure
package purecoincidence

object Purity extends App {
def pure(a: Int, b: Int): Int =
2*a*a + 3*b // 2a^2 + 3b

val p = pure(2, 3) // => 17

println((
p + p,
pure(2, 3) + p,
pure(2, 3) + pure(2, 3),
)) // => (34,34,34)
}
package purecoincidence

object PurelyLong extends App {
def pure(a: Int, b: Int): Int =
2*a*a + 3*b // 2a^2 + 3b

def expressionA(a: Int, b: Int): Int =
4*a*a + pure(a, b) + 6*b

def expressionB(a: Int, b: Int): Int =
3 * pure(a, b)

println((
expressionA(3, 4),
expressionB(3, 4)
)) // => (90,90)
}

(๋ชจ๋…ธ์ด๋“œ)monoidโ€‹

  • empty, combine
  • ๊ฒฐํ•ฉ๋ฒ•์น™, ํ•ญ๋“ฑ์›

์˜ต์…˜ ๋ชจ๋‚˜๋“œโ€‹

  • pure, flatMap
  • ์˜ต์…˜ ๋ชจ๋‚˜๋“œ - ๊ฐ’ ์œ ๋ฌด, ์—๋Ÿฌ ์œ ๋ฌด๋ฅผ ๋‹ค๋ฃจ๊ธฐ ๋ฌด์—‡์ด ํŽธํ•œ๊ฐ€.

FP ์‹ค๋ฌด์—์„œ ์“ฐ๋ ค ํ•œ๋‹ค๋ฉด... ์–ด๋ ต์ฃ โ€‹

์ƒ์‚ฌ, ๋™๋ฃŒ๋ฅผ ์„ค๋“, ์ดํ•ด๊ด€๊ณ„์ž ๋‹ค์ˆ˜๊ฐ€ FP์— ๊ด€์‹ฌ ์žˆ๊ธฐ๋Š” ์–ด๋ ต์ง€ ์•Š๊ฒ ๋‚˜? ์ด๋ฏธ ์ž˜ ๊ฐ–์ถฐ์ง„ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— ํฐ ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ๋Š” ์–ด๋ ต๊ณ , ํฐ ๊ทœ๋ชจ์˜ ํšŒ์‚ฌ์—์„œ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งก๋Š” ๊ฒƒ์€ ์‰ฝ์ง€ ์•Š์€ ์ผ.

์Šค์นผ๋ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌโ€‹

๋ช…๋ นํ˜•๊ณผ ํ•จ์ˆ˜ํ˜•์„ ํ˜ผํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. Cats
ํ•จ์ˆ˜ํ˜• ์›น ์„œ๋น„์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ http4s
JSON์€ circe (ํ•จ์ˆ˜๋ฅผ ์กฐ๋ฆฝํ•ด์„œ ์ธ์ฝ”๋” ๋””์ฝ”๋”๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ)
doobie cats๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“  RDB ์ ‘๊ทผ (Connection IO ๋ชจ๋‚˜๋“œ ์‚ฌ์šฉ)

๋‹น์‹ ์˜ ์—…๋ฌด์— FP ๋„์ž…โ€‹

๋ช…๋ นํ˜• ์–ธ์–ด์—์„œ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅโ€‹

ํ•จ์ˆ˜๋กœ ๊ฐ’์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ, ๊ณ ์ฐจํ•จ์ˆ˜ ํ™œ์šฉ. ํ•จ์ˆ˜ ์กฐํ•ฉ vavr.io (functional library for Java) ํšจ๊ณผ(effect)๋ฅผ ์ผ์œผํ‚ค๋Š” ํ”„๋กœ์‹œ์ ธ์™€ ์ˆœ์ˆ˜ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌ

ํ•จ์ˆ˜ํ˜• ์–ธ์–ด๋ฅผ ์ง์—…์ ์œผ๋กœ ์“ฐ๊ธฐ ์œ„ํ•œ ์กฐ๊ฑดโ€‹
  • ์ง€์†์ ์ธ ๊ด€์‹ฌ๊ณผ ๋ฐ”๋žจ
  • ๋‚ด๊ฒŒ ๊ทธ๋‹ฅ ์ค‘์š”์น˜ ์•Š์€ ๊ฑธ๋“ค์„ ํฌ๊ธฐํ•  ์šฉ๊ธฐ
  • ์ถฉ๋ถ„ํ•œ ์ž์‹ ๊ฐ
    • ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋กœ ์…€ํ”„ ํ›ˆ๋ จ
    • ์‹ค๋ฌด ํ”„๋กœ์ ํŠธ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์ ์ง„์  ๋ฐ˜์˜ํ•ด์„œ ๊ฒฝํ—˜์น˜
ํ˜‘์ƒ์˜ ์˜ณ๋ฐ”๋ฅธ ์ถœ๋ฐœ์ โ€‹
  • ์ €ํฌ ํ”„๋กœ์ ํŠธ์— ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๋„์ž…ํ•˜๋ฉด ์–ด๋–จ๊นŒ์š”? (์ ˆ๋Œ€ X)
    • ์ƒˆ๋กœ์šด ์‹œ๋„์˜ ๊ฐ€์น˜๋ฅผ ์ฆ๋ช…ํ•ด์•ผ ํ•˜๊ณ , ํž˜๋“  ๋ณ€ํ™”๋ฅผ ๋ฆฌ๋“œํ•ด์•ผ ํ•œ๋‹ค.
  • ๊ทธ๋ƒฅ ์กฐ์šฉํžˆ ๋ฌต๋ฌตํžˆ ์ €์ง€๋ฅธ๋‹ค. ๋˜๋Š”, ์ ์šฉํ•˜๊ณ  ๋‚˜์„œ ํ†ต๋ณด
    • ์ง€๋ฅด๊ณ  ๋‚˜์„œ, ์™œ ์•ˆ๋˜๋Š” ์ง€๋ฅผ ๋ฌผ์–ด๋ณด์ž.
    • ๋‹น๋‹นํ•˜์ง€ ๋ชป ํ•  ์ด์œ ๊ฐ€ ์—†๋‹ค.

๐ŸŽˆ ํ•จ์ˆ˜ํ˜• ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„ ๊ตฌํ˜„โ€‹

  • ์˜ˆ์ œ: ๊ฒฐ์ œ ์‹œ์Šคํ…œ ๋‘ ๊ฐ€์ง€ ํ• ์ธ ์œ ํ˜• ๋น„์œจ ํผ์„ผํŠธ ์ •์•ก ํ• ์ธ

Table Driven Design์˜ ๋ฌธ์ œโ€‹

๋†’์€ ๋ณต์žก๋„ ์ธ์ง€ ๋ถ€ํ•˜๊ฐ€ ๋Š˜์–ด๋‚จ

enum DiscountType:
case Percentage
case FixedAmount

class Discount(
var discountType: DiscountType,
var percentage: Option[Int],
var fixedAmount: Option[BigDecimal],
var fixedAmountCurrency: Option[Currency]
)

C(Discount) = C(discountType) * C(percentage) *
C(fixedAmount) * C(fixedAmountCurrency)

= C(DiscountType) C(Option[Int]) *
C(Option[BigDecimal]) * C(Option[Currency])

= 2 * 2 * 2 * 2
= 16
// Discount๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์˜ ์ˆ˜๋Š” 16๊ฐœ
  • ์ž˜๋ชป๋œ ๊ฐ’์— ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • ์œ ์ง€๋ณด์ˆ˜ํ•  ๋•Œ ์ƒ๊ธฐ๋Š” ์ผ
    • ํŠน์ • ํ• ์ธ ์œ ํ˜•์— ์ƒˆ๋กœ์šด ํ•„๋“œ
      • ๊ณ„์‚ฐ ๊ณผ์ •์— ๋ฐ˜์˜ํ•˜๋Š”๊ฑธ ๊นŒ๋จน์—ˆ๋‹ค
    • ์ƒˆ๋กœ์šด ํ• ์ธ ์œ ํ˜• ์ถ”๊ฐ€
      • else if ๋ฌธ ์ถ”๊ฐ€.

๋Œ€์ˆ˜์  ์ž๋ฃŒํ˜• (ADT)โ€‹

  • ADT๋ฅผ ์ด์šฉํ•œ ๋„๋ฉ”์ธ ๋ชจ๋ธ๋ง
enum Discount:
case Percentage(percent: Int)
case Fixed(amount: BigDecimal, currency: Currency)
ํ• ์ธ
- ๋น„์œจ: ํผ์„ผํŠธ(์ •์ˆ˜)
- ์ •์•ก: ์•ก์ˆ˜(์‹ค์ˆ˜), ํ†ตํ™”(ํ†ตํ™”์ฝ”๋“œ)
def discount(subtotal: Money, d: Discount): Money =
d match {
case Percentage(percent) => // amount ์ ‘๊ทผ ๋ถˆ๊ฐ€
case Fixed(amount, currency) => // percent ์ ‘๊ทผ ๋ถˆ๊ฐ€
}

ADT๋กœ ๋ชจ๋ธ๋ง ์‹œ ์žฅ์ 

  • ์œ ํšจํ•œ ์ƒํƒœ๋งŒ ํฌํ˜„ ๊ฐ€๋Šฅ
  • ๋ณต์žก๋„ ๊ฐ์†Œ

ADT ์ผ€์ด์Šค์˜ ๋ณต์žก๋„

  • ๋‘ ๊ฐ€์ง€ ๊ฒฝ์šฐ์—๋งŒ ์‹ ๊ฒฝ์„ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
enum Discount:
case Percentage(percent: Int)
case Fixed(amount: BigDecimal, currency: Currency)
C(Discount) = C(Percentage) + C(Fixed)
= C(percent) + (C(amount) * C(currency))
= C(Int) + (C(BigDecimal) * C(Currency))
= 1 + (1 * 1)
= 2

ํ…Œ์ด๋ธ” ์ฃผ๋„ VS. ํ•จ์ˆ˜ํ˜•

  • ํ…Œ์ด๋ธ” ์ฃผ๋„
enum DiscountType:
case Percentage
case FixedAmount

class Discount(
var discountType: DiscountType,
var percentage: Option[Int],
var fixedAmount: Option[BigDecimal],
var fixedAmountCurrency: Option[Currency]
)
  • ํ•จ์ˆ˜ํ˜•
enum Discount:
case Percentage(percent: Int)
case Fixed(amount: BigDecimal, currency: Currency)

EITHER ํƒ€์ž…์„ ์ด์šฉํ•œ ๋„๋ฉ”์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

Either ADT

enum Either[+A, +B]:
case Left(value: A)
case Right(value: B)

๋Ÿฌ์ŠคํŠธ์˜ Result๋„ ๊ฐ™์€ ๊ตฌ์กฐ

pub enum Result<T, E> {
Ok(T),
Err(E),
}

EITHER ํƒ€์ž…์€ ์šฐํŽธํ–ฅ

case class LengthError()

def oddLength(str: String): Either[LengthError, Int] =
str.length match
case n if n % 2 == 0 => Right(n)
case n => Left(LengthError())

var result =
for
s <- oddLength("scala") // Right(5)
h <- oddLength("haskell") // Right(7)
yield s + h

assert(result == Right(12))
var result =
for
s <- oddLength("scala") // Right(5)
j <- oddLength("java") // Left(LengthError())
h <- oddLength("haskell") // ํ‰๊ฐ€ํ•˜์ง€ ์•Š์Œ
yield s + h

assert(result == Left(LengthError()))

๋‹ค ์ข‹์€๋ฐ ์‹ค๋ฌด์—์„œ ์“ธ ์ˆ˜ ์žˆ๋‚˜?

  • ์˜ค๋ฅ˜ DB ์„ค์ • ๊ด€๋ฆฌ ์˜์กด์„ฑ ๊ด€๋ฆฌ
  • ์ด๊ฒƒ๋“ค์ด ๊ฐ€๋Šฅํ•œ๊ฐ€?

์ˆœ์ˆ˜ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋žจ์˜ ํŠน์ง•โ€‹

์ ˆ์ฐจ์ 

  • ๋ถ€๋ถ„์ : ๋ชจ๋“  ์ž…๋ ฅ์„ ์ฒ˜์ง€ํ•˜์ง€ ์•Š์Œ(์˜ˆ์™ธ)
  • ๋น„๊ฒฐ์ •์ : ๊ฐ™์€ ์ž…๋ ฅ, ๋‹ค๋ฅธ ์ถœ๋ ฅ
  • ๋น„์ˆœ์ˆ˜: ๋ถ€์ˆ˜ํšจ๊ณผ, ์‹คํ–‰ ์ค‘ ๊ฐ’์ด ๋ณ€ํ•จ

ํ•จ์ˆ˜ํ˜•

  • ์ „์ฒด์ : ๋ชจ๋“  ์ž…๋ ฅ์— ๋Œ€ํ•œ ์ถœ๋ ฅ์ด ์กด์žฌํ•จ
  • ๊ฒฐ์ •์ : ์ž…๋ ฅ๊ฐ’์ด ๊ฐ™์œผ๋ฉด ์ถœ๋ ฅ๊ฐ’๋„ ๊ฐ™์Œ
  • ์ˆœ์ˆ˜ํ•จ: ๋ถ€์ˆ˜ํšจ๊ณผ๊ฐ€ ์—†์Œ

์ˆœ์ˆ˜ ํ•จ์ˆ˜ํ˜• ์ดํŽ™ํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€์š”? ๊ฐ€๋Šฅ: ํ•จ์ˆ˜ํ˜• ์ดํŽ™ํŠธ ๋™์ž‘์›๋ฆฌ

ํ”„๋กœ๊ทธ๋žจ์˜ ๋ช…์„ธ์™€ ์‹คํ–‰์„ ๋ถ„๋ฆฌํ•œ๋‹ค. ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์ž‘์€ ํ”„๋กœ๊ทธ๋žจ์„ ํฐ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์กฐ๋ฆฝํ•œ๋‹ค.

๋น„์Šทํ•œ ์‚ฌ๋ก€

  • ํŒŒ์ด์ฌ ์Šคํฌ๋ฆฝํŠธ์™€ ์ธํ„ฐํ”„๋ฆฌํ„ฐ
  • Abstract Syntax Tree(AST)
  • Continuation-Passing Style (CPS)

์˜ˆ: console ADT

enum Console[+A]:
case Return(value: () => A)
case PrintLine(line: String, rest: Console[A])
case ReadLine(rest: String => Console[A])

console (CONT.1) ํ”„๋กœ๊ทธ๋žจ

val example1 = Console[Unit] =
PrintLine("์•ˆ๋…•ํ•˜์„ธ์š”, ์ด๋ฆ„์ด ๋ฌด์—‡์ธ๊ฐ€์š”?",
ReadLine(name =>
PrintLine(s"${name}๋‹˜, ๋ฐ˜๊ฐ€์›Œ์š”.",
Return(() => ()))))

console (count.2) ์ธํ„ฐํ”„๋ฆฌํ„ฐ
ํŒจํ„ด ๋งค์นญ์„ ํ†ตํ•ด ์‹คํ–‰

def interpret[A](program: Console[A]): A = program match {
case Return(value) =>
value()
case PrintLine(line, next) =>
println(line)
interpret(next)
case ReadLine(next) =>
interpret(next(scala.io.StdIn.readLine()))
}

ZIOโ€‹

  • ์ˆœ์ˆ˜ ํ•จ์ˆ˜ํ˜• ์ดํŽ™ํŠธ
  • ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ์•ˆ์ •์„ฑ
  • ๋™์‹œ์„ฑ, ์ŠคํŠธ๋ฆฌ๋ฐ
  • ํ™œ๋ฐœํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ƒํƒœ๊ณ„์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ
    • ์Šค์นผ๋ผ์˜ Spring
  • ์Šค์นผ๋ผ์˜ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ๋ฉด ํ™œ์šฉ
  • Cats๋“ฑ ๋‹ค๋ฅธ ํ•จ์ˆ˜ํ˜• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜ธํ™˜
์˜ค๋ฅ˜ ์ฒ˜๋ฆฌโ€‹
def findUserByName(name: String): IO[DBError, Option[user]]
def pureValidation(user: User): Either[DomainError, Unit]
def flakyApiCall(x: Int): IO[NetworkError, Remote]

def prog: ZIO[Has[Clock], AppError, Remote] =
for
user <- findUserByName("guersam")
.catchAll(e => IO.fail(AppError.Unexpected(e)))
.someOrFail(AppError.NotFound("User not found"))

_ <- IO.from(pureValidation(user))
.mapError(e => AppError.FromDomainError(e.msg))

๊ฐ์ฒด์ง€ํ–ฅ ์„ค๊ณ„ ๊ธฐ๋ฒ• ์žฌ์‚ฌ์šฉโ€‹

  • FP in small, OOP in large
  • ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„ ๋ถ„๋ฆฌ
trait UserRepo:
def getById(id: UserId): IO[DBError, User]

case class UserRepoLive(log: Logging, db: Database)
extends UserRepo:
// ...

case class TestUserLive(ref: Ref[Map[UserId, User]])
extends UserRepo:
// ...

์ •๋ฆฌโ€‹

  • ๋Œ€์ˆ˜์  ์ž๋ฃŒํ˜•์„ ์ด์šฉํ•œ ๋„๋ฉ”์ธ ๋ชจ๋ธ๋ง
    • ํ…Œ์ด๋ธ” ์ฃผ๋„ ์„ค๊ณ„์™€ ํ•จ์ˆ˜ํ˜• ์„ค๊ณ„
    • ํ†ต์ œ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ ๋ณต์žก๋„ ๊ด€๋ฆฌ
  • ZIO๋กœ ์‹ค์ œ๋กœ ์œ ์šฉํ•œ ํ”„๋กœ๊ทธ๋žจ ๋งŒ๋“ค๊ธฐ
    • ํ•จ์ˆ˜ํ˜• ์ดํŽ™ํŠธ ์‹œ์Šคํ…œ
      • ๋ช…์„ธ์™€ ์‹คํ–‰์„ ๋ถ„๋ฆฌ
    • ZIO
      • ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
      • ์˜์กด์„ฑ ๊ด€๋ฆฌ