자바 개발자에서는 scala에서 사용하는 map을 주로 쓰는 스타일(어쩌면 예시문으로 배운 패턴)이 있다.


List(1,2,3).map { i =>

  ...

}


그런데. map 안에 함수가 들어가는 형태가 좀 어색할 때가 있다. 




즉, map 안에 함수적 코드가 들어가는 것은 어색하지 않은데.. 


println(List(1,2,3).map( i => i + 1))




 map 뒤에 plus1이라는 함수를 추가하니까 엄청 어색하다. 

def plus1(s: Int) = s + 1
val a = List(1,2,3).map(plus1)
println(a)


두 결과는 다음처럼 동일하다.


List(2, 3, 4)

List(2, 3, 4)




다음은 string을 인자로 받는 예제이다. 

def convert(str: String) = s"${str}/tt"
val b1 = List("1","2","3").map(convert)
println(b1)
val b2 = List("1","2","3").map(convert).mkString(",")
println(b2)

결과는 다음과 같다. 



List(1/tt, 2/tt, 3/tt)

1/tt,2/tt,3/tt


어떤 List결과를 변형(map)하고, List는 ,로 바꾸는 경우이다.


파일 디렉토리를 사용할 때 아주 유용할만하다..


Posted by '김용환'
,



zeppelin으로 spark 연동시 주의 사항을 2가지 설명한다.




1. zeppelin과 spark/scala 버전 이슈



zeppelin 0.6.2를 사용하고 있는데, 


https://zeppelin.apache.org/download.html 에 따르면, 


scala 2.11 & spark 2.0 버젼과


scala 2.10 & spark 1.6 버전을 살 수 있다고 한다. 



  • Note: From Zeppelin version 0.6.2, Spark interpreter in binary package is compatible with Spark 2.0 & Scala 2.11 and Spark 1.6(or previous) & Scala 2.10. You can use even different version of Spark at the same time if you set different SPARK_HOME in interpreter setting page.



기본 버전이 아니면, zeppelin에서 아래와 같은 에러가 발생할 수 있다.

예를 들어, Scala 2.11에 Spark 1.6과 같은 버전을 쓰려고 할때. 에러가 발생한다. 



java.lang.NoSuchMethodError: scala.Predef$.ArrowAssoc(Ljava/lang/Object;)Ljava/lang/Object;


java.lang.NoSuchMethodError: scala.runtime.ObjectRef.create(Ljava/lang/Object;)Lscala/runtime/ObjectRef;




이럴 때는 문서에 있는데로, conf/zeppelin-env.sh에 SPARK_HOME=/usr/local/spark 에 추가하면 더 이상 해당 에러는 발생하지 않는다. 






2. zeppelin과 spark cluster 연동하기


zeppelin은 spark master 1대만 동작하도록 설정되어 있다.


spark 슬레이브를 여러 대로 해서(물론 config/slave 수정 필요) ./sbin/start-slave.sh <옵션> 실행해서 클러스터 모드를 만든다. 




참고로 아래와 같은 이슈가 만난다면 버전 이슈이고, 기존 데몬 다 내리고, 디렉토리 몽땅 지워야 한다. (버전 진짜 중요.)


java.lang.RuntimeException: java.io.InvalidClassException: org.apache.spark.rpc.netty.RequestMessage; local class incompatible: stream classdesc serialVersionUID = -5447855329526097695, local class serialVersionUID = -2221986757032131007


memory, cpu의 설정이 정말 중요하다. 노드당 사용할 메모리가 적절히 않으면, master는 대기할 하드웨어 자원이 나타날 때까지 기다린다.




zepelin에서 cluster mode로 설정한다. 


zeppelin의 scala interpreter->master 설정 -> local[*] 


이를 다음처럼 바뀌어야 한다. 


master spark://서버이름:7077


standalone은 간단하지만, mesos 모드거나 얀을 쓴다면 아래를 참고한다.


https://zeppelin.apache.org/docs/0.7.0-SNAPSHOT/install/spark_cluster_mode.html






3. zeppelin 실행 안되는 현상


zeppelin이 실행이 안되면, refresh 한다. 세션이 끊어지거나, zeppelin이 재시작된 경우에는 동작이 되지 않는다.


또는 아래와 같은 에러가 발생하면, zeppelin또는 spark을 재시작한다. 



The currently active SparkContext was created at:


(No active SparkContext.)




그리고, 실행은 되는데, pending이라면, spark 모니터링 서버를 살펴본다. 



http://서버이름:28080/


제대로 slave와 동작하는 지 확인해야 한다. 다양한 원인이 있을 수 있다. spark의 메모리나 CPU 설정 잘못하면, pending 상태로 남겨진다.


zeppelin의 이슈는 아니지만, spark설정을 제대로 해야하는 부분이 존재한다. 




4. 무거운 연산


무거운 연산은 zeppelin을 죽인다. 노트북으로서 좋은데, 진짜 대용량 데이터를 가지고 하기에는 아직은 버겹다..

Posted by '김용환'
,




sys.env는 환경 변수를 읽는 코드이다. 



scala> sys.env("PHASE")

java.util.NoSuchElementException: key not found: PHASE1



[~] export PHASE=Test



scala> sys.env("PHASE")

res0: String = Test




모든 환경 변수를 보고 싶다면, 아래 코드를 실행한다.


import scala.collection.JavaConversions._


val environmentVars = System.getenv()

for ((k,v) <- environmentVars) println(s"key: $k, value: $v")


val properties = System.getProperties()

for ((k,v) <- properties) println(s"key: $k, value: $v")


Posted by '김용환'
,





zeppelin에서 외부 라이브러리를 추가하는 방법은 여러가지가 있다.



1. maven 외부 repository, 내부 repository를 사용할 수 있다.


https://zeppelin.apache.org/docs/0.6.0/manual/dependencymanagement.html



2. jar를 직접 추가한다. (assemblied jar)


https://zeppelin.apache.org/docs/latest/interpreter/spark.html

conf/interpreter.json 파일에서 spark 의 zeppelin.dep.localrepo 속성을 찾고, 절대 위치로 바꾼다.

/usr/local/zeppelin/local-repo로 바꾸서 잘 안된다. 



무식하게 하는 방법이 나은 것 같다. /usr/local/zeppelin/local-repo의 jar 파일을 추가한다.


conf/zeppelin-env.sh 파일에서 export SPARK_SUBMIT_OPTIONS="--jars /usr/local/jeppelin/local-repo/custom-spark-job.jar" 을 추가하고 재시작하면 외부 라이브러리를 사용할 수 있다. 




Posted by '김용환'
,



scala에는 특정 타입을 만족하는 증거(witness)를 의미한다. 


implicit evidence는 허용할 타입을 제한함을 의미한다. 



간단하게 아래 내용을 테스트해본다.

val list1 = List("a" -> "b", "c" -> "d", "e" -> "f")
println(list1.toMap)

이 결과는 다음과 같다.


Map(a -> b, c -> d, e -> f)



toMap은 암시를 받는데, <:<를 사용한다. A는 (T, U) 타입의 서브 타입, 즉 튜플이어야 함을 의미한다. 


def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = {
val b = immutable.Map.newBuilder[T, U]
for (x <- self)
b += x

b.result()
}


만약, 아래 코드를 실행했다면, 에러가 발생했을 것이다. 


val list1 = List(1,2,3,4,5)

list1.toMap // Error:(8, 6) Cannot prove that Int <:< (T, U).



implicit을 쓰지 않아도 암시로 쓰일 수 있다. 



scala>   def impTest[X : Seq](x : X) = x

impTest: [X](x: X)(implicit evidence$1: Seq[X])X





Predef.scala의 일부 내용에 다음과 같이 쓰여져 있다. 

/**
* An instance of `A <:< B` witnesses that `A` is a subtype of `B`.
* Requiring an implicit argument of the type `A <:< B` encodes
* the generalized constraint `A <: B`.
*
* @note we need a new type constructor `<:<` and evidence `conforms`,
* as reusing `Function1` and `identity` leads to ambiguities in
* case of type errors (`any2stringadd` is inferred)
*
* To constrain any abstract type T that's in scope in a method's
* argument list (not just the method's own type parameters) simply
* add an implicit argument of type `T <:< U`, where `U` is the required
* upper bound; or for lower-bounds, use: `L <:< T`, where `L` is the
* required lower bound.
*
* In part contributed by Jason Zaugg.
*/
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
// The dollar prefix is to dodge accidental shadowing of this method
// by a user-defined method of the same name (SI-7788).
// The collections rely on this method.
implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

@deprecated("Use `implicitly[T <:< U]` or `identity` instead.", "2.11.0")
def conforms[A]: A <:< A = $conforms[A]

/** An instance of `A =:= B` witnesses that the types `A` and `B` are equal.
*
* @see `<:<` for expressing subtyping constraints
*/
@implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.")
sealed abstract class =:=[From, To] extends (From => To) with Serializable
private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
object =:= {
implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
}




Posted by '김용환'
,

[scala] 암시 implicitly

scala 2016. 11. 3. 14:02


암시(implicit)에 대한 기초 개념은 아래에서 정리해봤다.

http://knight76.tistory.com/entry/scala-class-%EC%98%88%EC%8B%9C-2-%EC%95%94%EC%8B%9C%EC%A0%81-%EB%B3%80%ED%99%98implicit-conversion



Predef에 implicitly 라는 게 존재한다. 



@inline def implicitly[T](implicit e: T) = e


implicit을 짧게 쓸 수 있는 관용구 정도로 생각하면 될 것 같다. 



def plus(i: Int) (implicit j: Int) = println(i + j)
def plusWrapper(i: Int) = plus(i)
def plusImplicitly(i: Int) = plus(i)(implicitly[Int])

implicit val j = 20
plus(10)
plusWrapper(20)
plusImplicitly(30)


결과는 다음과 같다.


30

40

50


Posted by '김용환'
,



scala의 문자열과 연관된 패턴 매치에 대한 예시이다.


자바는 정규 표현식과 관련된 클래스가 Pattern, Matcher가 있지만, scala에서는 scala.util.matching.Regex 클래스가 존재한다. 패턴을 정의하고, 문자열에 대한 패턴 정보를 findAllIn 메소드(findXXX 시리즈로 다양한 제어를 할 수 있다)로 확인할 수 있다. 


val pattern1 = new scala.util.matching.Regex("(H|h)ello")
val str = "Hello Mom, hello Dad"
println(pattern1.findAllIn(str).mkString(","))

결과는 다음과 같다. 


Hello,hello




하지만, 꼭 scala.util.matching.Regex를 명시해서 사용할 필요는 없다. 정규 표현식으로 사용할 표현식에 .r을 붙이면 알아서 Regex 타입의 인스턴스가 생성된다. 

val numPattern = "[0-9]+".r
// scala.util.matching.Regex = [0-9]+
val str1 = "Hello Mom 911, hello Dad 119?"
val matches = numPattern.findAllIn(str1)
println(matches.toList)


결과는 다음과 같다.


List(911, 119)




Regex의 findFirstIn을 사용하면 Option으로 리턴한다. 




val numPattern = "[0-9]+".r val str1 = "Hello Mom 911, hello Dad 119?"

println(numPattern.findFirstIn(str1))
println(numPattern.findFirstIn(str))


결과는 다음과 같다. 


Some(911)

None




Option의 특징을 활용해서 Some과 None에 대한 case 문을 작성할 수 있다.


numPattern.findFirstIn(str1) match {
case Some(s) => println(s)
case None => println("None")
}

numPattern.findFirstIn(str) match {
case Some(s) => println(s)
case None => println("None")
}


결과는 다음과 같다. 


911

None




이번에는 반대로, 특정 문자열에 대해 regex 타입을 case 문에 넣을 수 있다.  그리고, 

// string match
val sp = "[a-zA-Z]".r
val np = "xxx".r
"bus 1503, 5, 222" foreach {
case sp => println("s : " + sp)
case np => println("n : " + np)
}



결과는 다음과 같다. 


s : b

s : u

s : s

s :  

s : 1

s : 5

s : 0

s : 3

s : ,

s :  

s : 5

s : ,

s :  

s : 2

s : 2

s : 2






case 문이나 find 메소드 없이 아주 간단하게 정규 표현식을 읽을 수 있다. 

val regex = "(\\d+)/(\\d+)/(\\d+)".r
val regex(year, month, day) = "2016/11/01"
println(year, month, day)

결과는 다음과 같다. 


(2016,11,01)





그리고, 정확치 않은 정규 표현식이라면, 적당히 알아서 나온다(뒤에서 부터 파싱되었다).

val regex1 = """(\d+)(\d+)(\d+)""".r
val regex1(a, b, c) = "112801"
println(a, b, c)

결과는 다음과 같다.

(1128,0,1)




간단한 문자열도 이와 같이 실행할 수 있다. 

val Name = """(\w+)\s+(\w+)""".r
val Name(firstName, lastName) = "samuel kim"
println(firstName, lastName)


"samuel kim" match {
case Name(first, last) => println(first, last)
case _ => println("x")
}

위 두 결과는 모두 다음과 같다. 


(samuel,kim)

(samuel,kim)





이젠 full name도 쉽게 할 수 있다. 

val FullName = """(\w+)\s+(\w+)\s+(\w+)""".r
val FullName(first, middle, last) = "samuel jackson kim"
println(first, middle, last)

결과는 다음과 같다.


(samuel,jackson,kim)




시간, 분, 초를 한번에 로그에 남겼을 경우 이를 예쁘게 보여줄 수 있는 함수이다. 


val pattern = """([0-9]{1,2})([0-9]{1,2})([0-9]{1,2})""".r
val allMatches = pattern.findAllMatchIn("112801")
allMatches.foreach { m =>
println(m.group(1) + ":" + m.group(2) + ":" + m.group(3))
}

결과는 다음과 같다. 


11:28:01




이를 Spark-Zeppelin으로 간단하게 보여주기 위해 getTime이라는 메소드를 만들었다. 



val reqLog = textFile.filter(line=>line.contains("12314")).map(s=>s.split("\t")).map(

    s=>RequestLog(getTime(s(1).toString),

            s(5), 

            s(11),

            s(21)

        ))




하나는 Regex에 findAllMatchIn과 foreach, group을 사용한 함수와

또 다른 하나는 Regex에 findAllIn과 case를 이용한 방식을 사용했다. 

def getTime(time: String): String = {
val pattern = """([0-9]{1,2})([0-9]{1,2})([0-9]{1,2})([0-9]{1,5})""".r
val allMatches = pattern.findAllMatchIn(time)
var result = ""
allMatches.foreach { m =>
result = m.group(1) + ":" + m.group(2) + ":" + m.group(3) + "." + m.group(4)
}
result
}

def getTime2(time: String): String = {
val pattern = """([0-9]{1,2})([0-9]{1,2})([0-9]{1,2})([0-9]{1,5})""".r
var result = ""
pattern.findAllIn(time) foreach {
case pattern(hour, minute, second, millisecond) => result = hour + ":" + minute + ":" + second + "." + millisecond
}
result
}



println(getTime("1128000000"))
println(getTime2("1128000000"))

결과는 다음과 같다. 


11:28:00.0000

11:28:00.0000



Posted by '김용환'
,



* 부작용(side effect)


함수가 결과값 이외에 다른 상태를 변경시킬 때 부작용이 있다고 말한다. 문서에서 side effect에 대해서 부정적으로 봐서는 안된다. 



* 순수 함수

부작용이 없는 함수



* 참조 투명성(referential transparency)


참조 투명성(referential transparency)은 부작용(side effect)이 없음을 표현하는 속성이다. 

입력 값에 대해 항상 같은 값을 돌려주는 것 외에 다른 기능이 없다. 내부적으로 관리되는 변수(상태)에 영향이 없는 것을 말한다. 



참조 투명성에 대한 예제는 다음과 같다.  항상 매개변수를 주면, 그 값을 언제 어디서든 그 값을 기대할 수 있다. 


scala> def sum(x: Int, y: Int): Int = x + y

sum: (x: Int, y: Int)Int


scala> sum(sum(1, 2), 3)

res2: Int = 6


scala> sum(3, 3)

res3: Int = 6





하지만, 변수(상태)값과 의존성이 있는 코드가 있다고 하자. sum 메소드를 호출하면, 계속 값이 변경된다. 이를 참조 투명성이 없다고 말할 수 있다. 

scala> var m = 0
m: Int = 0

scala> def sum(x: Int, y: Int): Int = { m = m + x + y; m}
sum: (x: Int, y: Int)Int

scala> sum(sum(1, 2), 3)
res10: Int = 21

scala>  sum(3, 3)
res11: Int = 27

scala>  sum(3, 3)
res12: Int = 33




sum 메소드는 바로 값을 기억하고 연산에 사용되기 때문에, 순수 함수가 아니고 참조 투명성이 없다. 


함수형 프로그래밍 언어에서는 참조 투명성을 극히 중요하는 것 같아서, 함 정리했다. 





나중에 scalaz의 Task와 Future를 설명하겠지만.. 

scala의 Future는 계산한 후 Future의 결과를 기억하는 반면, 

scalaz(외부 라이브러리)의 Task는 unsafePerformSync를 호출할 때마다 계산을 수행한다. 


다른 말로 말하면, Task는 참조 투명성이고 Future보다는 함수형 프로그래밍 패러다임에 가깝다.





이런 내용을 잘 설명한 내용이 하스켈의 참조 투명성 자료이다. 

https://wiki.haskell.org/Referential_transparency


Posted by '김용환'
,

scala에서 REPL을 사용하려면 간단하게 scala를 실행하면 된다.


$ scala 



그리고, 필요한 내부 라이브러리는 다음처럼 import문을 사용한다.


scala> import scalaz._




만약 외부 라이브러리(lib)를 어떻게 입력해야 하는가?



그러기 위해서는 sbt를 활용할 수 있다. 



sbt 프로젝트에서 sbt를 실행해서 아래처럼 간단하게 사용할 수 있다.  (참고로 sbt는 디렉토리 기반이기 때문에 global 설정이 아니다)


$ sbt



set scalaVersion := "2.11.8"

set libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.2.0"

set initialCommands := ""

set initialCommands += "import scalaz._, Scalaz._, scala.concurrent._, scalaz.concurrent._, java.util.concurrent._"

session save

console



console을 실행하면 다음과 같은 로그가 생성된다. lib을 로딩하고,import 문이 자연스럽게 만들어진다. 


[info]

import scalaz._

import Scalaz._

import scalaz.concurrent._

import java.util.concurrent._

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_101).

Type in expressions for evaluation. Or try :help.



실행 환경을 구성한다. 

scalaz.concurrent.Promise는 외부 라이브러리이지만, REPL에서 사용할 수 있다. 

scala> val p = Promise[Int]
p: scala.concurrent.Promise[Int] = List()


scala> p.future.value
res19: Option[scala.util.Try[Int]] = None


scala>  p.success(20)
res20: p.type = Success(20)

scala> p.future.value
res21: Option[scala.util.Try[Int]] = Some(Success(20))





만약 중간에 외부 라이브러리를 읽어야 한다면, Ctrl + D를 누르고,  다시 모드 환경에서 내용을 설정한다.


ctrl + D를 눌러 REPL import문을 다시 구성할 수 있다. 


scala> // 여기서 Ctrl + D


[success] Total time: 371 s...



> set initialCommands := ""

> set initialCommands += "import scalaz._, Scalaz._, scala.concurrent._, scalaz.concurrent._, java.util.concurrent._, java.util.collections._ "





Posted by '김용환'
,

[scala] Array, WrappedArray

scala 2016. 10. 29. 07:52


Array는 변경이 가능하고 값에 인덱스를 가진 컬렉션이다. 


배열은 랜덤 접근에 대해 Vector 대비  실제로 상수 시간의 복잡도를 제공한다. 하지만, Vector에 반해 Array는 메모리의 단일 및 연속 덩어리로 한 번에 할당된다. 그리고, Array는 엘리먼트의 앞과 뒤에 추가할 수 있는 오퍼레이션을 지원하지 않는다. 


scala의 Array는 java Array를 활용한 방식을 사용하고 있다. 그럼에도 불구하고 성능이 좋다. 추가 기능이 없지만, 상황에 따라서 Vector보다 더 좋을 수 있다. 



http://docs.scala-lang.org/overviews/collections/performance-characteristics.html


                             headtailapplyupdateprependappendinsert
ArrayCLCC---



Array에 대한 간단한 예시이다. 



var crew = Array("John","Samuel","Matt")
crew(0) = "Juno"
for (c <- crew) {
print(c + " ")
}
println()

Predef
var student = new Array[String](5)
student = crew
for (s <- student) {
print(s + " ")
}
println



결과는 다음과 같다.


Juno Samuel Matt 

Juno Samuel Matt 






특이할 점은 Array는 implicit을 잘 활용한다. Array와 WrappedArray가 되었다. 



scala> val a = Array(1, 2, 3, 4, 5)

a: Array[Int] = Array(1, 2, 3, 4, 5)


scala> val b:Seq[Int] = a

b: Seq[Int] = WrappedArray(1, 2, 3, 4, 5)



WrappedArray는 Array로 돌릴 수 있다. 


scala> val c = b.toArray

c: Array[Int] = Array(1, 2, 3, 4, 5)



그리고, 처음 배열과 비교해 본다. 동일한 값이다! 


scala> a==c

res3: Boolean = true





이제 Array.reverse 메소드를 적용해보자. Array는 reverse 되어도 Array타입이 되고, 6을 추가할 수 있다. 


scala> a.reverse

res4: Array[Int] = Array(5, 4, 3, 2, 1)


scala> a :+ 6

res5: Array[Int] = Array(1, 2, 3, 4, 5, 6)



WrappedArray.reverse 메소드를 적용하자. WrappedArray를 reverse하면 WrappedArray 타입이 되는데, 6을 추가할 수 있다. 하지만, 타입이 ArrayBuffer로 바뀌었다. ( Seq임을 잘 살펴본다)



scala> b.reverse

res6: Seq[Int] = WrappedArray(5, 4, 3, 2, 1)


scala> b :+ 6

res7: Seq[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6)



implicit이 발생한 것이다. 




WrappedArray의 선언을 살펴보자.


*  A class representing `Array[T]`.
*
* @tparam T type of the elements in this wrapped array.
*

abstract class WrappedArray[T]
extends AbstractSeq[T]
with IndexedSeq[T]
with ArrayLike[T, WrappedArray[T]]
with CustomParallelizable[T, ParArray[T]]
..




Predef 에 보면, Array에 대한 implicit 함수가 있는 것이 눈에 보인다..

implicit def wrapRefArray[T <: AnyRef](xs: Array[T]): WrappedArray[T] = {
if (xs eq null) null
else if (xs.length == 0) WrappedArray.empty[T]
else new WrappedArray.ofRef[T](xs)
}

implicit def wrapIntArray(xs: Array[Int]): WrappedArray[Int] = if (xs ne null) new WrappedArray.ofInt(xs) else null
implicit def wrapDoubleArray(xs: Array[Double]): WrappedArray[Double] = if (xs ne null) new WrappedArray.ofDouble(xs) else null
implicit def wrapLongArray(xs: Array[Long]): WrappedArray[Long] = if (xs ne null) new WrappedArray.ofLong(xs) else null
implicit def wrapFloatArray(xs: Array[Float]): WrappedArray[Float] = if (xs ne null) new WrappedArray.ofFloat(xs) else null
implicit def wrapCharArray(xs: Array[Char]): WrappedArray[Char] = if (xs ne null) new WrappedArray.ofChar(xs) else null
implicit def wrapByteArray(xs: Array[Byte]): WrappedArray[Byte] = if (xs ne null) new WrappedArray.ofByte(xs) else null
implicit def wrapShortArray(xs: Array[Short]): WrappedArray[Short] = if (xs ne null) new WrappedArray.ofShort(xs) else null
implicit def wrapBooleanArray(xs: Array[Boolean]): WrappedArray[Boolean] = if (xs ne null) new WrappedArray.ofBoolean(xs) else null
implicit def wrapUnitArray(xs: Array[Unit]): WrappedArray[Unit] = if (xs ne null) new WrappedArray.ofUnit(xs) else null





Array를 사용할 때, 다른 스칼라 컬렉션에서 사용 가능한 대부분의 메소드를 사용할 수 있다. 


implicit 변환은 Array을 ArrayOps와 WrappedArray를 확대하는데 사용된다. ArrayOps은 Array에 대한 간단한 래퍼 클래스이다. 


ArrayOps는 임시로 Array에 인덱스를 가진 순서 집합에서 찾을 수 있는 모든 오퍼레이션을 사용할 수 있게 해주는 간단한 Array 래퍼 클래스이다. 


ArrayOps의 메소드를 호출하면 Array를 리턴한다. 









Posted by '김용환'
,