Scala의 Ant/Maven/Gradle와 비교될 수 있음. 직접으로 프로젝트 생성, 배포 관여.
https://github.com/sbt/sbt-assembly
# Build (Fat Jar 생성) ## build.sbt에 ## addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.6") ## 추가 필요 ### 생성된 jar 위치 : target/scala-[version]/*.jar sbt assembly
# 실행 scala scala> 1 res0: Int = 1 # 종료 scala> :quit ## 혹은 scala> :q
- Intellij에 기본적으로 SBT 내장. Intellij만 설치하면 된다.
왼쪽 Project Tree에서 아무 Class에 오른쪽으로 누르고 “Run Scala Console”.
// class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! args.foreach(print) println()
> scala script.scala Hello , world ! Hello, world!
object HelloWorld extends App { // class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! args.foreach(print) println() }
> scalac HelloWorld.scala > scala HelloWorld Hello , world !
object HelloWorld { def main(args: Array[String]): Unit = { args.foreach(print) println() } }
> scalac HelloWorld.scala > scala HelloWorld Hello , world !
식별자 규칙 및 관례적 명명법 서술.
def *** (v: Int) = math.pow(v, 3)
:(Cons; Construct)로 끝나는 연산자는 right-associative. 이외 나머지는 left-associative
// left-associative a * b * c = (a * b) * c = a.*(b).*(c) // right-associative a ::: b ::: c = a ::: (b ::: c) = b.:::(c).:::(a)
Alphanumeric + Operator. 예를 들어 myvar_=. Scala Compiler가 Property를 지원하기 위해 내부적으로 생성.
역따옴표(`)를 사용하며 Runtime에서 인식할 수 있는 문자열 생성. 이를 이용하면 예약어도 변수나 Method 이름으로 지정 가능.
// 아래 코드는 Compile 및 실행에 문제가 없다. val `val` = 3; def show(`var` : Int) = println(`var`); show(`val`)
Any | 모든 type의 supertype. equals, hashCode, toString 메서드가 정의됨. |
---|---|
AnyVal | value type을 표현, non-nullable. Double, Float, Long, Int, Short, Byte, Char, Unit, Boolean의 상위 타입. |
AnyRef | referenced type을 표현, java.lang.Object에 대응. |
Nothing | 모든 type의 subtype (i.e. bottom type)으로 value를 가지고 있지 않다. |
Null | 모든 reference type의 subtype. keyword literal 'null'을 값으로 가지고 있다. |
큰따옴표 3개(""")를 이용하여 그 내부 문자열을 “날 것” 그대로 표현하는 문자열. (공백, 개행 전부 포함)
object Test extends App { // Java에서의 일부 특수문자 표현. 가독성이 좋지 않음. val str1 = "Hello\n\"world\""; println(str1) /** * Hello * "world" */ // 공백, 개행 모두 그래도 출력 val str2 = """Hello, "world"! """ println(str2) /** * Hello * "world" */ // |를 넣으면 문자열의 시작을 알림. 이후 stripMargin을 하면 이전 공백 제거 val str3 = """ |Hello, |"world!" """.stripMargin println(str3) /** * Hello * "world" */ }
문자열 내부에 표현식(expression)을 내장시키는 것. 이로써 문자열을 이어붙이지 않고 가독성이 좋아짐.
Description | Example | |
---|---|---|
s | “$val” 혹은 “${expression}“과 함께 쓰여 mapping. | val str = "9 * 9 = " println(s"$str ${9 * 9}") /** * 9 * 9 = 81 */ |
raw | 문자열 그대로 출력. | println(raw"A\nB\\C\tD\bE") /** * A\nB\\C\tD\bE */ |
f | printf 형태의 형식 지정. ${expression}%[format] | println(f"${math.Pi}%.7f") println(f"${3 * 3}%03d") /** * 3.1415927 * 009 */ |
val fruits = new Array[String](2) // val fruits: String = new Array[String](2) fruits(0) = "apple" // fruites.update(0, "apple") fruits(1) = "banana" // fruites.update(1, "banana")
val fruits = Array("apple", "banana") // val fruits = Array.apply("apple", "banana")
Scala에서 모든 연산자는 Method!! 실질적으로 Scala는 Method를 호출한다.
/** Binary Operator **/ val sum = 1 + 2 // 1.+(2) "Hello, world!" indexOf 'o' // "Hello, world!".indexOf('o') "Hello, world!" indexOf ('o', 5) // "Hello, world!".indexOf('o', 5) /** Unary Operator **/ /*** 전위 표기법 ***/ // +,0,!,~ 4가지. val a = -2.0 // (2.0).unary_- /*** 후위 표기법 ***/ // 인자를 취하지 않는 Method를 '.'이나 괄호 없이 호출 val str = "Hello, world!" toLowercase // val str = "Hello, world!".toLowercase()
Java와 동일하다.
==, !=은 원시 타입에서 값(value)이 같은지 비교, eq/ne는 JVM Heap에서 참조(reference) 비교
scala> List(1, 2, 3) == List(1, 2, 3) res8: Boolean = true scala> List(1, 2, 3) eq List(1, 2, 3) res9: Boolean = false scala> List(1, 2, 3) ne List(1, 2, 3) res10: Boolean = true // 주의! scala> ("He" + "llo") == "Hello" res11: Boolean = true
scala> 111 max 222 res0: Int = 222 scala> 111 min 222 res1: Int = 111 scala> -math.Pi abs res2: Double = 3.141592653589793 scala> -math.Pi round res3: Long = -3 scala> (1.0 / 0) isInfinity res4: Boolean = true scala> 1 to 100 res5: scala.collection.immutable.Range.Inclusive = Range 1 to 100 scala> "hello" capitalize res6: String = Hello scala> "Hello, World" drop 7 res7: String = World
전체적으로 재귀나 내장함수(reduce, map, filter, reduce)로 대체가 가능하기 때문에 사용을 자제하자. 제어 구문은 함수형 표현에 적합하지 않다.
foreach 문만 존재한다. 그러나 대체로 반복하는 경우 내장 함수(foreach, reduce, map, filter 등)이 존재하기 때문에 특별한 경우가 아니면 큰 필요성이 없다.
/* Collections */ //10 ~ 100 for (i <- 10 to 100) println(i) //10 ~ 99 (최대값 제외) for (i <- 3 until 10) println(i) /* Iterator Guard / Filter. if 표현식이 true일 때만 반복 */ for (file <- new java.io.File(".").listFiles() if file.isFile if file.getName.endsWith(".scala")) println(file) /* 중첩 및 임시 변수 Binding */ // 구구단 출력 for {x <- (1 to 9) y <- (1 to 9) result = x * y} println(f"$x * $y = $result%02d")
<EXPRESSION>의 값을 Collection로 반환. (map 과 같은 효과)
for (<IDENTIFIER> <- <ITERATOR>) yield <EXPRESSION>
// 인자가 없기 때문에 ()를 생략한 형태. def timesTable = for { x <- (1 to 9) y <- (1 to 9) result = x * y} yield f"$x%2d * $y%2d = $result%2d" println(timesTable) // Vector( 1 * 1 = 1, 1 * 2 = 2, ... , 9 * 8 = 72, 9 * 9 = 81)
try { val f = new FileReader(".") } catch { case ex: FileNotFoundException => ex.printStackTrace() case ex: IOException => ex.printStackTrace() } finally println("done")
// 결과 : 2 // Java 형식으로 finally 절 안에 return문을 명시적으로 사용하면 try/cath의 결과를 덮어쓰게 된다. // 잘 생각해보면 Java에서 finally는 어떤 결과로든 항상 실행되는 구문이기 때문. def test1(): Int = try return 1 finally return 2 // 결과 : 1 // Scala 형식으로 작성하면 의도된대로 가장 처음에 만나는 값을 반환한다. def test2(): Int = try 1 finally 2 // 결론적으로 finally에는 값을 사용하지 말자.
case의 대안. Java와 달리 타입에 상관없이 어떤 상수값이라도 사용 가능. 이 때, match 앞에 오는 비교대상의 타입을 기본적으로 다른다. (그렇게 되면 case로 비교하는 값들도 그 타입을 따른다.) '_'은 default에 대응하며 “완전히 알려지지 않은 값”을 의미.
object Client extends App { def matchTest(x: Any): Any = x match { case 1 => "one" case "two" => 2 case y: Int => "scala.Int" case _ => 'unknown } println(matchTest(3)) // scala.Int }
def sum(x: Int, y: Int): Int = { x + y } // 위와 같은 표현 def sum(x: Int, y: Int) = x + y
// Unit 함수. Return 값이 없음을 의미. // Type 추론이 되므로 Return Type에 Unit 생략 가능. def greeting(): Unit = println("Hello, world!")
함수의 중첩이 가능.
def test1(x: Int) = { def test1(y: Int) = x * y test1(10) } println(test1(5)) // 50
위치 표시자.
/* 고차함수를 더 간략하게 작성하는 방법 */ val values = 1 to 10 values.filter((x: Int) => x > 5) // filter의 인자는 Int를 받도록 명시되어 있으므로 생략 가능 values.filter(x => x > 5) // 위치표시자(_)를 이용하면 더 간략한 표현 가능. values.filter(_ > 5) // 아래와 같은 응용도 된다. values.foreach(println _) // values.foreach(x => println(x)) // 그런데 foreach의 인자가 명확한 위치가 나타나기 때문에 생략 가능 values.foreach(println) val func1 = (_:Int) + (_:Int) println(func1(1, 1)) // 2
def sum(a: Int, b: Int, c: Int) = a + b + c // sum 함수를 대입. 이 때, _의 의미는 sum의 모든 인자를 의미. val a = sum _ println(a(1, 2, 3)) // = a.apply(1, 2, 3) // 아래와 같이 부분적인 인자 적용도 가능. val b = sum(1, _:Int, 3) println(b(2)) // = b.apply(2)
/* Java의 경우 final(immuitable)로 선언해야만 접근 가능한 것에 비해 var(mutable)도 사용 가능. */ /* Javascript와 동일. */ /* 변수 바인딩 */ var sum = 0 (1 to 10).foreach(sum += _) ;; 바깥 범위(scope)에 있는 sum의 값에 누적. println(sum) // 55 /* 함수 바인딩 */ def scope1(x: Int) = { /*def scope2(y: Int) = x + y scope2 _*/ // 위와 같은 표현 x + (_: Int) } // 각 scope1에 바인딩된 바깥쪽 x를 다르게 설정 val closure1 = scope1(1) val closure2 = scope1(2) println(closure1(10)) // 11 println(closure2(10)) // 12
/* repeated parameter. 가변 인자(variable paramter)와 동일 개념 */ def test1 (args: String*) = args.foreach(println) test1("Hello", "world", "!") test1(Array("Hello", "world", "!"): _*) /* named argument. 순서 상관없이 인자의 이름으로 전달 */ def test2 (v1: Int, v2: Int) = v1 + v2 println(test2(v2 = 1, v1 = 9)) /* default argument. 인자의 값이 없을 경우 default로 값을 미리 지정 */ def test3 (v1: Int = 1, v2: Int) = v1 + v2 println(test3(v2 = 1))
함수를 1급 객체로 취급하기 때문에 가능.
다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것.
def curriedSum(x: Int)(y: Int) = x + y //def curriedSum(x: Int) = (y: Int) => x + y println(curriedSum(1)(2))
값, 함수 모두 “값”으로 평가하면서 인자를 넘기고 싶은 경우 사용.
이 역시 함수를 1급 객체로 취급하기 때문에 가능.
paramter의 Lazy evalution. 성능상의 이점을 가진다.
def test1(b: Boolean) = b test1(2 > 1) // by-name parameter. lazy evaluation. def test2_1(b: () => Boolean) = b test2_1(() => 2 > 1) // 개선 def test2_2(b: => Boolean) = b test2_2(2 > 1)
기본적으로 불변(Immutable) 상태를 가진다.
선형 자료 구조.
Singly Linked List. 앞부분에 추가/삭제 유리. 임의 접근은 불리.
[새 원소] :: [기존 List]
val test1 = List(1, 2, 3) val test1 = 1 :: 2 :: 3 :: Nil
val test1 = List(1, 2) ::: List(3, 4) ::: List(5, 6) // List(1, 2, 3, 4, 5, 6) val test2 = 1 :: List(2, 3) // List(1, 2, 3) val test3 = List(1, 2) :: List(2, 3) // List(List(1, 2), 2, 3)
head | tail | apply | update | prepend | append | |
---|---|---|---|---|---|---|
List | C | C | L | L | C | L |
Vector | eC | eC | eC | eC | eC | eC |
결론적으로 head가 임의 위치 접근이 필요한 경우 Vector 선택.
import scala.collection.mutable val set1 = Set("a", "b") // 원소 추가 // Collection이 val인 경우, mutable package의 import 필요! /// set1.+=("b") 와 같은 표현 set1 += "b"
// immutable이기 때문에 원소를 추가할 수 없음. 이어붙이는 연산을 해야 함. val map1 = Map("A" -> 1, "B" -> 2, "C" -> 3) val map2 = Map("D" -> 4, "E" -> 5, "F" -> 6) val map3 = map1 ++ map2 println(map3) // 기본 구현체는 immutable.HashMap // HashMap(E -> 5, F -> 6, A -> 1, B -> 2, C -> 3, D -> 4)
import scala.collection.mutable val map1 = Map[String, Int]() map1 += ("One" -> 1) map1 += ("Two" -> 2) map1("One")
val map1 = Map("One" -> 1, "Two", 2) map1("One")
둘 다 동시성을 보장하는 Thread-Safe Map.
엄격히 말하면 Collections에 포함되지 않음.
val tp = (123, "Hello", 1.23) // val tp = Tuple3[Int, String, Double](123, "Hello", 1.23) print(tp._1) print(tp._2) print(tp._3)
Modifier | Class | Companion | Subclass | Package |
---|---|---|---|---|
public (default) | Y | Y | Y | Y |
protected | Y | Y | Y | N |
private | Y | Y | N | N |
class Person { // member 변수와 Getter/Setter 서술 }
// 아래와 같이 class를 선언하면 member 변수들은 private val // 이후 값을 변경할 수 없음. class Person (name: String, age: Int) { def getName: String = name def getAge : Int = age }
// 아래와 같이 class를 선언하면 member 변수들은 public val /// val을 선언함으로써 Getter 생성. ".name", ".age"로 접근. // 바로 멤버 변수에 접근할 수 있고, 이후 값을 변경할 수 없음. // Class 뒤의 인자들을 "Class Parameter"라고 지칭한다. // Scala Compiler는 내부적으로 Class Parameter를 기반으로 Primary Constructor(주 생성자)를 생성한다. /// 기본적으로 Scala의 Constructor는 Class Parameter를 생성하지 않는다. val을 붙이면 생성한다. class Person (val name: String, val age: Int)
// Scala Compiler는 // Class 내부에 있으면서 // Field나 Method 정의에 들어 있지 않은 코드를 // Primary Constructor에 삽입한다. /// 객체 생성과 동시에 println 메시지 출력. class Person (name: String, age: Int) { println("Created!" + name + " / " + age) }
class Person (val name: String, val age: Int) { def this(age: Int) = this("meteor", age); override def toString: String = name + " / " + age }
class Person (name: String, age: Int) { require(name != null, "name is not nullable.") require(age > 0) override def toString: String = name + " / " + age }
Operator Overloading.
class Person (val name: String, val age: Int) { def +(p: Person) = this.toString + "\n" + p.toString override def toString: String = name + " / " + age }
object Client extends App { val p1 = new Person("hyuk", 30) val p2 = new Person("meteor", 26) println(p1 + p2) }
Java와 동일하나 Scala는 Type을 유추하기 때문에 적합한 Method의 Parameter Type 일치 결과가 없다면 'ambiguous reference' 오류를 출력. 그리고 Overloading보다 DEFAULT PARAMETER VALUES를 이용을 권장. Method 수를 줄일 수 있다.
'Default Method' 또는 'Injector Method'라고도 불린다. 이는 Method 이름없이 괄호를 사용하여 호출하는 기능을 갖고 있다.
val l = List(1, 2, 3) // 같은 의미 println(s"${l.apply(1)} ${l(1)}")
위 예제의 'List.apply(index)' 처럼 상식적으로 이해할 수 있는, 자연스러운 연산에 적용되어야 한다.
// Companion class class Person (val name: String, val age: Int) // Companion object object Person { def print(person: Person) :Unit = println(person.name + " / " + person.age) }
object Client extends App { val person: Person = new Person("Amy", 11) Person.print(person) } /* Amy / 11 */
어떤 class 이름이 같은 object. 그 피대상인 class는 “Companion class”라고 한다. Companion object와 'apply' Method를 이용하여 Companion class의 Factory Pattern 을 적용할 때 사용.
이 때, object의 field들이 “private”으로 선언되어 있더라도 접근 가능.
Companion class와 Companion object는 반드시 같은 소스 파일에 있어야 한다!
object DBConnection { private val url = "jdbc://localhost" private val user = "franken" private val password = "berry" def apply() = new DBConnection() } class DBConnection { private val props = Map( "url" -> DBConnection.url, "user" -> DBConnection.user, "password" -> DBConnection.password ) } // val connection = DBConnection()
이름 | 위치 | 설명 |
---|---|---|
apply | object | case class를 Instance로 만드는 Factory method |
unapply | object | Instance의 field들을 Tuple로 추출하여 Pattern Matching에 case class instance를 사용할 수 있도록 함 |
copy | class | Deep copy! |
equals | class | Reference가 아닌 구조적 동등성(모든 필드가 일치)으로 비교. '=='로도 호출 할 수 있다. |
hashCode | class | Instance의 field들의 Hash code 반환. hash 기반 collections에서 동등성에 이용. |
toString | class | Field들을 String으로 전환 |
object Practice extends App { val person = Person("Luke", 33) val copy = person.copy() // Equals? println(person == copy) person.name = "James" person.age = 11 // Deep copy? println(copy) // Pattern Matching val p = person match { case Person(_, 11) => "Original!" case Person(_, 33) => "Copy!" } println(p) } case class Person(var name: String, var age: Int)
Java의 경우 1.5 기준으로 @Override를 명시할 수 있으나 필수사항이 아닌데 반면, Scala는 강제한다. 이로 인해 “우연한 Override”로 “fragile base class”가 생성되는 문제를 줄여준다.
class Person (val name: String, val age: Int) { override def toString: String = name + " / " + age }
Java와 비교하여
abstract class Abstract { /* abstract가 필요하지 않다. */ def values: Array[Int] def sum = values.sum } class Detail(conts: Array[Int]) extends Abstract { /* 아래 동작은 같다. */ //override def values: Array[Integer] = conts val values: Array[Int] = conts }
같은 이름의 생성자 Paramter와 Field를 동시 정의하는 단축 표기. 특정 Field에 할당하는 이름 중복을 피하기 위한 Boilerplace 제거.
abstract class Abstract { def values: Array[Int] } // 아래와 같이 같은 목적이지만 중복된 이름을 피하기 위해 불필요한 부분이 작성된다. // 할당과 동시에 바로 getter 생성 class Detail(vals: Array[Int]) extends Abstract { override def values: Array[Int] = vals } // 이를 아래와 같이 작성할 수 있음. class Detail2(var values: Array[Int]) extends Abstract
Java의 경우 상속을 받으면 반드시 부모 Class의 생성자와 같은 형태의 생성자를 호출하고 super()를 하는 과정을 아래와 같이 간략화할 수 있다.
abstract class Abstract { def values: Array[Int] def sum = values.sum } class Detail(val values: Array[Int]) extends Abstract /* class Detail(vals: Array[Int]) extends Abstract { override def values: Array[Int] = vals } */ /* Super class(Detail)의 생성자 호출. */ class MoreDetail(x: Int) extends Detail(Array(x)) { }
Java의 Interface의 역할을 하는 한 단위.
object Client extends App { val x:D = new D x.func // "안녕, 세상아!" } trait T1 { def func(): Unit } trait T2 { private val greeting = "Hello, world!" def func() = println(greeting) } class C1 { } class D extends C1 with T1 with T2 { override def func(): Unit = println("안녕, 세상아!") }
object Client extends App { val queue = new BasicIntQueue with Doubling queue.put(10) println(queue.get()) // 20 } abstract class IntQueue { def get(): Int def put(x: Int) } class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] override def get(): Int = buf.remove(0) override def put(x: Int) = buf += x } trait Doubling extends IntQueue { // trait에서 super로 접근할 때는 override abstract가 필요. override abstract def put(x: Int): Unit = super.put(x * 2) }
가장 오른쪽에 있는 Trait의 효과부터 적용된다.
trait Base { override def toString: String = "Base" } trait A extends Base { override def toString: String = s"A->${super.toString}" } trait B extends Base { override def toString: String = s"B->${super.toString}" } trait C extends Base { override def toString: String = s"C->${super.toString}" } class MixIn extends A with B with C { override def toString: String = s"MixIn->${super.toString}" } // println(new MixIn()) /// MixIn->C->B->A->Base
Type Parameter를 특정 Class나 거의 Sub type 또는 Base type으로 제한하는 방법.
Type을 Base type 또는 Sub type 중 하나로 제한한다.
https://docs.scala-lang.org/tour/upper-type-bounds.html
<identifier> <: <upper bound type>
object Practice extends App { def check[A <: Parent](u: A): Unit = { println(s"${u.name}") } // Runtime Error // inferred type arguments [GrandParent] do not conform to method check's type parameter bounds [A <: Parent] check(new GrandParent("Jake")) check(new Parent("Fred")) check(new Child("Mike")) } class GrandParent(val name: String) class Parent(name: String) extends GrandParent(name) class Child(name: String) extends Parent(name)
Type을 해당 Type으로 제한하거나 또는 해당 Type이 확장되는 Base type 중 하나로 제한한다.
<identifier> >: <lower bound type>
Type parameter를 덜 제한적으로 만드는 방법.
타입 가변성(Type variance)은 Type parameter가 Base type이나 Sub type을 충족하도록 적응하는 방법을 지정한다.
Scala | Java | 설명 | 예시 |
---|---|---|---|
+T | ? extends T | Covariance (공변성) | X[Tsub]는 X[T]의 Sub type |
-T | ? super T | Contravariance (반공변성) | X[Tsuper]는 X[T]의 Sub type |
T | T | Invariance (무공변성) | X[T]는 X[T]. X[Tsub] 혹은 X[Tsuper]가 X[T]를 대체할 수 없음. |
// https://github.com/deanwampler/programming-scala-book-code-examples/blob/master/src/main/scala/progscala3/objectsystem/variance/FunctionVariance.scala class CSuper { def msuper() = println("CSuper") } class C extends CSuper { def m() = println("C") } class CSub extends C { def msub() = println("CSub") } object AbsTypes extends App { // 추상적인 것 -> 구체적인 것 val f1: C => C = (c: C) => new C val f2: C => C = (c: CSuper) => new CSub val f3: C => C = (c: CSuper) => new C val f4: C => C = (c: C) => new CSub // Error! /** 인자로 C를 받도록 타입을 정의했는데, C를 상속받은 CSub를 인자로 받는다면 * C가 알지못하는 Method가 호출될 수 있다! */ val f5: C => C = (c: CSub) => new CSuper }
https://docs.scala-lang.org/tour/abstract-type-members.html
import java.io._ import scala.io.Source // https://github.com/deanwampler/programming-scala-book-code-examples/blob/master/src/main/scala/progscala3/typelessdomore/AbstractTypes.scala // 위 소스를 응용한 예제 abstract class BulkReader { type In // 구체적인 타입을 지정하지 않음. val source: In // 바로 위에서 지정한 Type인 "In"을 사용 def read: String } class StringBulkReader (val source: String) // 추상 타입의 실질 타입 정의 extends BulkReader { type In = String // 추상 타입 구체화 override def read: String = source } class FileBulkReader (val source: File) // 추상 타입의 실질 타입 정의 extends BulkReader { type In = File // 추상 타입 구체화 override def read: String = { val source1 = Source.fromFile(source) try source1.mkString finally source1.close() } }
어떤 상황에서 무엇이 적절한가?
Currying 혹은 Lazy 된 함수의 인자를 명시적으로 지정하지 않아도 자신의 네임스페이스의 기본값을 사용하는 방법. 변수나 함수 매개변수에 “implicit”를 앞에 붙인다.
“Dependency Injection”의 시각으로 볼 수 있다.
object Printer { def print(num: Double)(implicit format: String) = println(format.format(num)) } // fmt가 주입된다. object Practice extends App { implicit val fmt = "%.7f" Printer.print(1.11) }
암시적 Type 변환. 어느 Instance의 Type이 특정 Instance의 Type인 것처럼 자동 전환하여 그 특정 Instance의 Method를 가진 것처럼 보이게 할 수 있다.
C#, Kotlin의 Extension Methods과 비슷한 기능.
서로를 고려하지 않고 독립적으로 개발된 소프트웨어/라이브러리를 하나로 묶으려고 할 때 사용.
object Client extends App { val uc = new UnaryCalculator(2) val op1 = uc ** 3 //val op1 = uc.**(3) val op2 = 3 ** uc // Error! //val op2 = {3}.**(uc), 정수 3은 ** Method를 가지고 있지 않음. println(op1 + " / " + op2) } class UnaryCalculator (val v: Int) { def ** (`pow`: Int) = math.pow(v, `pow`) }
object Client extends App { implicit def intToUnaryCalculator (initVal: Int) = new UnaryCalculator(initVal) val uc = new UnaryCalculator(2) val op1 = uc ** 3 //val op = uc.**(3) val op2 = 3 ** uc //val op = {3}.**(uc) println(op1 + " / " + op2) // 8.0 / 9.0 } class UnaryCalculator (val v: Int) { def ** (`pow`: UnaryCalculator) = math.pow(v, `pow`.v) }