- https://github.com/enshahar/BasicFPinScala/blob/master/Intermediate/Monad.md

- https://github.com/enshahar/BasicFPinScala/blob/master/Intermediate/Monad_Rules.md

Monad Programming with Scala Future

http://www.haruair.com/blog/2986

http://blog.seulgi.kim/2015/07/what-is-monad.html

- 자바언어로 서술한 Functor 와 모나드 

 제 능력이 부족한 관계로 위의 글로써 대신합니다. (모나드 관련 한글 블로그 모음) 

 아참! 위의 글을 읽기 전에 반드시 모나드 괴담!을 읽어보세요. ^^


모나드가 무엇인지 이해함으로써 하스켈 프로그래머가 되겠다는 것은 "악기란 무엇인가?"를 이해하면 모든 악기의 연주자가 될 수 있으리라는 믿음과 같다. 즉 모나드를 이해하는것과 프로그래밍을 잘하는것과는 별개의 문제.
Fallacy (2015)


모나드 간단 정리
(코딩하다 보면 아 대략 이러면 모나드다 정도면 될 거 같습니다. 완벽한 모나드의 법칙을 이해 할 필요까지는 없을듯) 

1.모나드는 다른 타입을 인자로 받는 타입이다. 

case class Boxed[T](value:T);

2.모나드 타입의 값을 생성하는 함수가 있어야 한다.

def initBoxed(x:Int):Boxed[Int] = Boxed(x)
def initLogged(x:Int):Logged[Int] = Logged(x, List())
def initMyOption(x:Int):MyOption[Int] = MySome(x)

3.다른 모나드 타입으로 진행하는 함수가 있어야 한다. (또한 감싼 값에 대해서 꺼낼 수 있는 방법을 제공하며 꺼낸 값을 가지고 원하는 형식으로 변형하고 다시 감싸서 반환)

session => Future[User]
=> flatMap => (User => Future[Order])
=> flatMap => (Order => Future[List[Item]])

위의 3가지 성질을 모두 가지고 있을때 모나드라 칭한다.

* 스칼라 Future 는 모나드가 아니다.=> https://stackoverflow.com/questions/27454798/is-future-in-scala-a-monad


아래의  자바스크립트의 예를 보면 대략 모두 가지고 있음을 알 수 있다. 

// 타입 스크립트로 Monad의 인터페이스를 설계함
interface MStatic<T> {
    // constructor that wraps value
    new(value: T): M<T>;
}

interface M<T> {
    // bind as an instance method
    bind<U>(transform: (value: T) => M<U>): M<U>;
}


 


스칼라에서 JSON 데이터 다루기

* scala 기존제공하는것보다 json4s 나 spray-json 이 더 나은듯하다.


1. 디펜던시 추가 

import scala.util.parsing.json._


2. 문자열에서 Json 객체 (Map 타입) 로 변경  - (1)


def main(arg : Array[String]): Unit ={


val result = JSON.parseFull("""
{"name": "Naoki", "lang": ["Java", "Scala"]}
""")

result match {
case Some(e) => println(e)
case None => println("Failed.")
}

} //print 결과 : Map(name -> Naoki, lang -> List(Java, Scala)


3. 문자열에서 Json 객체 (Map 타입) 로 변경  - (2)

def main(arg : Array[String]): Unit ={


val msg = """

{ "callid" : 1 ,
"gatewayid" : "gatewayid-1" ,
"switchid" : "switch-id" ,
"content" : "hi"
}
"""

val result = JSON.parseFull(msg)

result match {
case Some(e) => val res1 = e.asInstanceOf[Map[String,Any]];
print(res1.get("callid"))
case None => println("Failed.")
}

} //print 결과 : Some(1.0)

asInstanceOf 를 통해서 형변환을 수행 하였다.


case Some(map: Map[String,Any]) => print(map.get("callid"))

Some 내부에서 형변환 할 수 도 있다.


4. Json 객체 (Map 타입) 에서 문자열로 변경 


의존성 추가 ( json4s 를 이용함) 

libraryDependencies += "org.json4s" %% "json4s-native" % "3.2.10"
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.2.10"

import 

import org.json4s.jackson.Serialization

map 을  String 으로 변경

implicit val formats = org.json4s.DefaultFormats

Serialization.write(mutableMap)


 

Partially applied function 과 Partial function 

먼저 아래 링크 참고 하세요. (아웃사이더님이 번역하신  글이 있습니다)

https://blog.outsider.ne.kr/953  


아마 저 글을 읽어도 Partial function에 관해 조금 헤깔리실텐데요. 간략하게 다시 설명해 드리겠습니다.
함수란 모든 x 에 대해서 y 라고 할때 부분 함수란 일부 x 에 대해서 y 인 함수를 말 합니다. 즉 제한을 둔겁니다.

예를들어

function  A (int x ) {
   return x * x 
}

라는 함수에 대해서 아래 함수는 부분함수입니다.

function A'  (int x) {
  if x > 10 
     return x * x 
}

모든 것을 처리하지 않고 싶을 때 부분 함수를 만들게 됩니다.
그 모든 것 말고 예외인 항목을 입력하면 에러/예외가 발생하게 말이죠.

그 예외 항목이 먼지 알려주는 함수가 isDefinedAt 입니다.
따라서 이 함수를 먼저 이용해서 해당 파라미터가 그 부분함수에서 잘 작동하는지 확인한 후에 
파라미터를 넘긴다는 야그죠. 

아래 예제를 봅시다.

scala> List(41, "cat") map { case i: Int ⇒ i + 1 }
scala.MatchError: cat (of class java.lang.String)

 "cat" 이 map 으로 전달되면 case 에 매칭되지 않기 때문에 에러가 나는데 

scala> List(41, "cat") collect { case i: Int ⇒ i + 1 }
res1: List[Int] = List(42)

Collect 경우는 괜찮네요? 

네 Collect 경우는 isDefinedAt 으로 부분함수에 대한 검증을 합니다. 

마지막으로 PartialFunction 은 아래와 같이 만들며 자동으로 isDefinedAt 이 만들어지는게 특징입니다.

[Int, Int] 는 인자로 Int 리턴값으로 Int 를 말합니다.

val fraction: PartialFunction[Int, Int] =
  { case d: Int if d != 042 / d }

이 경우 isDefindAt 에 0 이 들어가면 false 를 내보내겠지요?

마지막 예제를 보면서 생각해 보는 시간을 가지세요 ^^

scala> val liar: PartialFunction[Any, Int] =
  { case i: Int ⇒ i; case s: String ⇒ s.toInt }

liar: PartialFunction[Any,Int] = <function1>

scala> liar.isDefinedAt(42)
res10: Boolean = true

scala> liar.isDefinedAt("cat")
res11: Boolean = true

scala > liar ("3")
res12: Int = 3 

scala> liar("cat")
java.lang.NumberFormatException: For input string: "cat"

scala> val honest: PartialFunction[Any, Int] = { case i: Int ⇒ i; case s: String if isParsableAsInt(s) ⇒ s.toInt } honest: PartialFunction[Any,Int] = <function1> scala> honest.isDefinedAt("cat") res12: Boolean = false



언더스코어  _  는 스칼라에서 어떻게 사용되나?


1. Import 


import scala.concurrent._

import 에서 _ 는 모든을 뜻한다. 자바에서 * 


2. 디폴트 초기화 

class Foo {
var i : Int = _ // i = 0
var s: String = _ // s = null

}

Int  는 0 으로 초기화 되고 String 을 대입하면 null 로 초기화 됩니다.


3. 고계 함수에서 익명 파라미터


( 1 to 10) map { _ + 1 }


4. 부분 함수에서 익명 파라미터

좀 더 정확히 얘기하면 partially applied function.   partial function 과 구분된다. 바로가기 

def f(i : Int) : String = i.toString
def g = (x: Int) => f(x)
def g2 = f _
def u(i: Int) (d: Double) : String = i.toString + d.toString

def w = u(4) _ // (y: Double) => u(4)(y) 와 동일
println(w(5)) // 45.0 출력


5. 패턴 매칭

expr match {
case List(1,_,_) => "첫번째 요소는 1 이며 3개의 요소를 가진 리스트"
case List(_*) => "0 개나 혹은 더 많은 요소를 가진 리스트"
case Map[_,_] => "아무 타입의 키와 아무 타입의 값을 갖는 맵"
case _ => "나머지 아무거나"
}


6. 제네릭에서 와일드카드 

def size(objs: List[_]) : Int = {
objs.size
}

def loggedIn(req: RequestWithAttributes[_]): User = {

...

}


7. 프로퍼티에서 세터 

class Test {
private var a = 0
def age = a
def age_=(n:Int) = {
require(n>0)
a = n
}
}
val t = new Test
t.age = 5
println(t.age)


참고 :

 http://ananthakumaran.in/2010/03/29/scala-underscore-magic.html

http://www.slideshare.net/normation/scala-dreaded


Null 과 친구들 

Null  –  Trait 이다. 모든 참조 타입(AnyRef를 상속한 모든 클래스) 의 서브클래스이다. 값 타입과는 호환성이 없다. 

null  –  Null 의 인스턴스이고 자바의 null 가 비슷. val i : Int = null; 불가능 (값 타입과 호환성 없다) 

NothingTrait 이며 모든것들의 서브타입이다.기술적으로 예외는 Nothing 타입을 갖는다. 이 타입은 값이 존재하지 않는다. 값에 대입이 가능하다. 즉  리턴타입이 Int 인 메소드에서 리턴을 Nothing 타입인 예외를 던질 수 있다. 

Nil    –  아무것도 없는 List 를 나타낸다.

None – 아무것도 없다는 리턴 값을 표현하기 위해 사용된다. null 포인트 예외를 회피하기 위해 Option[T] 의 자식클래스로 사용된다. 

Unit  – 아무 값도 리턴 하지 않는 메소드의 리턴타입으로 사용. 



Apply

자바를 하다가 스칼라로 넘어오면 희안한거 많이 보는데 apply 라는 것도  분명히 마주치게 되는데 이게 여러군데에서  다른 의미로 사용되서 좀 헤깔리죠. 그래서 이 참에 정리 한번 하고 넘어 갑니다. 


기본 

해당 객체 뒤에 하나 이상의 값을 괄호{}로 둘러싸서 호출하면 그 객체의 apply() 메소드를 자동으로 호출해 줍니다. 스칼라에서는 apply 라는 메소드가 기본적으로 이 객체(타입) 저 객체(타입) 에 각자 정의되어 있습니다.함수타입에서 apply 하면 이렇게 작동하고 , 리스트에서  apply 하면 요렇게 작동하고 그렇습니다. 중구난방이라는 말 그래서 어느정도는 외워야합니다.

예를 보시죠

예1)

class applyTest {
def apply(n : Int): Unit = {
print ("hello world")
}
}

object test {

def main(arg : Array[String]): Unit ={

val at = new applyTest
at{10}
}
}

그냥 at{10} 로 호출하면 hello world 가 찍힙니다. 참고로 매개변수가 아예 없이는 안됩니다.

결국 {..} 사이에는 apply 의 인자가 들어가게 됩니다. 위에는 Int 형이 들어가네요.

예2)

class applyTest{

def apply( f : => Int): Unit ={
val n = f + 3
println("n = %d".format(n))
}
}


object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
println("apply test")
10
}
}
}

이게 참 골때리는 겁니다. 편의성을 높힌 Syntactic sugar 라고 하지만 여러언어를 쓰다보면 꽤 헥깔립니다.

먼저 applyTest 클래스에 apply 함수를 만들어 두었습니다. 이 apply 는  => Int 같은 람다식 (입력인자가 없고 리턴이 Int 형인) 함수타입을 인자로 받습니다. 여기까진 OK~

근데 10은 멀까요 -.-a

def apply( f : => Int)

여기에 f 에 대응되는 함수가 

println("apply test") 

return 10 

란 야그죠.

결국 f + 3 은 10 + 3 과 같습니다.


예3) 함수 자체를 넘기기도 합니다.

class applyTest{

def apply( f : String => Unit): Unit ={
f("hello world")
}
}


object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
println
}
}
}


예4) 함수의 리턴값을 넘기기도 하고 

object obj1{
def getWord: String = {
return "hello world"
}

}

class applyTest{

def apply( str : String): Unit ={
println(str)
}
}

object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
obj1.getWord
}
}
}

예4) 역시 이렇게 함수 자체를 넘길 수도 있습니다.


object obj1{
def getWord: String = {
return "hello world"
}

}

class applyTest{

def apply( block: => String): Unit ={
print(block)
}
}

object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
obj1.getWord
}
}
}

Array 에서의 apply 

예 1)  Array 에서의 값 가져오기

val a = new  Array[String](2)  // String 타입이 2개 담길수 있는 배열의 객체를 만들었네요.

a(0)  이렇게 하면 첫번째  값을 가져 올 수 있는데요. 객체에 바로 () 를 붙혔습니다. 어색하죠? 

즉 여기선  a.apply(0) 이렇게 자동으로 변경 됩니다.

그리고 Array 에서의 apply 메소드의 역할은 해당 인덱스에 해당하는 값을 꺼내오는 겁니다.


예 2Array 생성하기 

val a = Array("hello", "world") 


이렇게 만들 수 있습니다. new 가 생략되었는데 이게 가능한 이유는 

암시적으로 apply()  라는 이름의 팩토리 메소드를 호출해서 객체를 만들어줍니다.

Array.apply("hello", "world") 이렇게 되는거죠 여기서는 ..




함수타입에서 사용

val f1 = (x: Int, y: Int) => x + y
f1(2,3) // f1.apply(2,3) 와 같다. 결과 : Int = 5

저렇게 호출하면 알아서 apply 해준다.
참고로 저 함수 리터럴의 타입은 <function2> 이며 스칼라에선 모든게 다 객체이다.



Set 에서의 apply

val fruit = Set("apple", "orange", "peach")
fruit("peach")

위에서 호출되는 apply 는 contains 와 동일하다.




Play2 에서 Action 호출에서의 사용 

스칼라언어 기반 Play 프레임워크 웹개발에서는 

def doSomething = Action{
Ok.apply(views.html.index("Hi there"))
}

이런 모양새로 이루어지는데 여기서 Action 이 무엇일까요? 

위의 예는 다음과 같이 풀어지게 되는데요

def doSomething: Action[AnyContent] = Action.apply(
    Ok.apply(views.html.index("Hi there"))
)

위의 apply 메소드의 정의는  apply(block: => Result) 이라 익명의 함수가 들어가게 됩니다.  

이 말인 즉,  Ok.apply 함수는 Result 를 반환하겠군요.

Action.apply 함수는 Result 를 사용한 후에 Action[AnyContent] 를 반환하구요.



Option

값이 있거나 또는 없거나 한 상태를 나타낼 수 있는 타입이다.
값이 담겨져 있는 Option 의 하위 타입은 Some[T] 이며, 값이 없으면 None  이다.
Option 은 Try, Future 등과 함께 대표적인  모나딕컬렉션 이다. "컬렉션" 이다.

보통 Option 을 떠올리면 2가지를 생각해야한다.

1. null 을 안전하게 대체하기 위해 만들어진 것. 
-> 사용자에게 주의를 다시 한번 당부하는 것으로, null 예외가 발생할 확률을 없앤다.

2. 연속체인에서 안정적으로 사용하기 위한 것
-> 연속으로 계산되는 상황에서 안정적으로 실행된다. 즉 중간에 문제가 생기는것을 방어한다. 주의 할 것은 방어가 되는 함수는 따로 있다는 것이며 아래 표에서 자세히 설명된다. 

Option 이 사용되는 경우 

val numbers = Map("one" -> 1, "two" -> 2)
val h2 : Option[Int] = numbers.get("two")

이 경우 h1 에는 값이 담겨져 있는 Option 을 받을 것이다. 즉 Some 타입을 받을 것이고

val numbers = Map("one" -> 1, "two" -> 2)
val h3 : Option[Int] = numbers.get("three")

이 경우 h3 에 해당 되는 값이 없기 때문에 Option 은 None 이 될 것이다. 

Option 에서 값 가져오기 (get 과 isDefined 사용하기)

val h3 : Option[Int] = numbers.get("three")

이 경우 h3 이 Some 인지 None 인지 어떻게 알까? 확인하고 사용해야지 않을가? 
그때 isDefined 메소드를 사용한다. 
(반대 개념의  isEmpty 도 있다.)

if (h3.isDefined)
println (h3.get)

요렇게  확인해서 값을 사용 할 수 있다.  Option 타입에서 실제 값을 가져오는 함수는 get 이다. 

getOrElse 로 사용하기

def testOption(o : Option[Int]) = {

println(o.getOrElse("nothing"))
}

getOrElse 를 사용하여 값이 있으면 그 값을 사용하고 , 없으면 인자로 넘긴 "nothing" 을 사용하게 한다.

패턴 매칭으로 로 사용하기

def testOption(o : Option[Int]) = {

val result = o match {
case Some(n) => n
case None => "nothing" }
}

match 를 사용하여 값이 있으면 즉 Some(n) 으로 매칭되면 값 사용 , 없으면 "nothing" 을 사용하게 한다. 

Option 타입의 파라미터 사용

def testOption(o : Option[Int]) = {
    ...
}

testOption(Some(10))

옵션타입을 인자로 받는 함수에 값을 넘길때는 값을 직접 넘길 수 없다. Some 타입으로 만들어서 넘긴다.

Option 타입을 map 으로 연결 해보자.

case class Employee(name: String, department: String)

def lookupByName(name: String) : Option[Employee] = ...

val joeDepartment: Option[String] = lookupByName("joe").map(_.department)

이렇게 map 으로 연결 할 수 있다. 즉 map 은 Option 타입을 스스로 변경하여 적용한다.
근데 여기서 None 이 맵으로 들어 가면 어떻게 될까? 그냥 나머지 계산이 취소되어서 아무것도 안한다.

Option 은 스칼라에서의 가장 기본적인 모나드라고 한다.
추가적으로 Try, Future 등도 대표 모나딕컬렉션이다. 

안전한 Option 추출 정리 

 fold

nextOption.fold(-1)(x=>x) 

Some(이 경우 내장값)인 경우 주어진 함수로부터 추출한 값을,None인 경우 시작값을 반환함,foldLeft,foldRight,reduceXXX 메소드로도 Option 을 그 내장된 값 아니면 계산된 값으로 축소할 수 있음   

 getOrElse

nextOption getOrElse 5 또는
nextOption getOrElse {
   println("error!"); -1 }

Some의 값을 반환하거나 아니면 이름 매개변수의 결과를 반환함 

 orElse

nextOption orElse nextOption 

실제로 값을 추출하지는 않지만,None인 경우 값을 채우려함. Option이 비어 있지 않으면 이 Option을 반환하고, 그렇지 않은 경우 주어진 이름 매개변수로부터 Option 을 반환함 

match 표현식 

nextOption match { case Some(x) =>x;
                       case None => -1 } 

값이 있는 경우 매치 표현식을 사용하여 그 값을 처리함, Some(x) 표현식은 그 데이터를 추출하여 매치 표현식의 결과값으로 사용되거나 또 다른 변환에 재사용할 수 있는 지정된 값'x' 에 넣음 

* 러닝스칼라 (제이슨스와츠,제이펍 발생) 에서 발췌

Either 

둘 중 하나의 값을 가질 수 있는 타입이다.

Either 에 값 담기 

def eitherTest(num: Option[Int]): Either[String, Int] = num match {
case Some(n) => Right(n)
case None => Left("Error! Number is missing!")
}


eitherTest(Some(7))

eitherTest 메소드를 살펴보자. 
인자로 제대로 된 값이 들어오면 Right 즉 Either 의 오른쪽에 담고
인자에 제대로 된 값이 안 들어오면 Left 즉 Either 의 왼쪽에 값을 담는다.  

Either 값 사용하기 

val res = eitherTest(Some(7))
if( res.isRight )     println("Number is %s".format(res.right.get))
else if( res.isLeft)     println("Error message => %s".format(res.left.get))

eitherTest 메소드를 호출해서 받는 값 res 는  Either[String, Int] 타입인데  어느쪽에 제대로 된 값이 들어 있는 지 확인하기 위해서 res.isRight / res.isLeft 를 사용했다.

패턴 매칭으로 사용하기 

val result = eitherExample(Some(7)) match {
case Right(num) => num
case Left(err) => err
}

다음 처럼 패턴 매칭을 사용 할 수 도 있다. 


레퍼런스:

러닝 스칼라
프로그래밍 인 스칼라


List 활용


*  List 는 스칼라에서 가장 많이 사용되는 데이터 구조일 겁니다.

* 스칼라의 리스트는 디폴트로 불변형입니다.

* 스칼라의 리스트 타입은 공변적입니다.


길이 구하기

val a = List (1,2,3) 

a.length  // 3 


*배열과 달리 리스트의 length 는 비교적 비싼 연산입니다. 

a.isEmpty 를 a.length == 0 으로 사용하지 마세요. 


양 끝에 접근하기

val a = List ('a','b','c','d')

a.head     // 'a'  처음

a.last       //  'd'  마지막

a.tail       // List('b', 'c','d')  처음 제외 나머지  

a.init      // List('a','b','c')    마지막 제외 나머지 


리스트 뒤집기 

val a = List ('a',b','c','d')

a.reverse    // List ('d','c','b','a')  * 주의할것은 새로운 리스트가 생긴다는 점이다. 리스트는 불변이니깐~ 


drop / take / splitAt

val a = List ('a',b','c','d')

a.take(2)    // List ('a','b') 
a.drop(2)   // List ('c',d')

a.splitAt(2)  // (List('a','b'), List('c','d'))


apply / indices

val a = List ('a',b','c','d')

a.apply(2)   // 'c'      * 배열에 비해 불리하다.

a.indices    // Range (0,1,2,3)   * 리스트에서 유효한 모든 인덱스의 리스트를 반환 


flatten

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

a.flatten    // List (1,2,3,4,5)   * 리스트의 리스트일 경우에 한 해 하나로 반듯하게 펼친다.


zip / zipWithIndex

val a = List (1,2,3)  

val b = List('a','b','c') 

a.zip(b)     // List( (1,'a'), (2,'b') , (3,'c') )   * 두 리스트를 순서쌍으로 묶는다.

b.zipWidhIndex   // List ( ('a',0), ('b',2), ('c', 3) )  * 리스트의 원소를 인덱스와 함께 순서쌍으로 묶는다.


toString / mkString

val a = List ('a','b','c','d')

a.toString   // List(a, b, c, d)  * 리스트 자체를 표준 문자열로 만든다. 

a.mkSring( "[" , "," , "]" )   // [a,b,c,d,e]    * 리스트의 앞 , 중간, 뒤에 들어갈 문자를 정해서 출력한다.



toArray / copyToArray

val a = List ('a','b','c','d')

val arr = a.toArray   // Array(a,b,c,d)   * array로 변환

arr.toList   // List (a,b,c,d)   * List 로 변환


List  클래스의 고차 메소드


map 

val a = List (1,2, 3)

a.map(_ + 1)   //   List (2,3,4) 


flatMap

val words = List ( "the" , "quick" , "brown" , "fox" )

words.map(_.toList)      // List (List (t,h,e) , List (q,u,i,  .........

words.flatMap(_.toList)   //  List (t,h,e,q,u,i ...........



map  과 flatMap의 차이

scala> val fruits = Seq("apple", "banana", "orange")
fruits: Seq[java.lang.String] = List(apple, banana, orange)

scala> fruits.map(_.toUpperCase)
res0: Seq[java.lang.String] = List(APPLE, BANANA, ORANGE)

scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)

map  과 flatMap의 차이2  (이 특성은 너무너무너무 중요하다. 별표3개)

def toInt(s: String): Option[Int] = {
    try {
        Some(Integer.parseInt(s.trim))
    } catch {
        // catch Exception to catch null 's'
        case e: Exception => None
    }
}

scala> val strings = Seq("1", "2", "foo", "3", "bar")
strings: Seq[java.lang.String] = List(1, 2, foo, 3, bar)

scala> strings.map(toInt)
res0: Seq[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)

scala> strings.flatMap(toInt)
res1: Seq[Int] = List(1, 2, 3)

scala> strings.flatMap(toInt).sum
res2: Int = 6

map 은 타입을 감싼 타입을 그대로 내보내기 때문에 None 도 내보내지지만, flatMap 은 감싼 타입을 벗겨내기 때문에 None은 없어진다. 이 중요한 특성을 이용해서 예러(예외)가 없는 결과값을 연속적으로(함수합성)으로 사용하는게 가능해진다.  

flatMap의 추가 특징1

scala> val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> three)

scala> 1 to map.size flatMap(map.get)
res0: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(one, two, three)


flatMap의 추가 특징2

flatMap 은 map 이랑 flatten 을 합해놓것과 같다. 예를들어 List[List[Int]] 를 List[Int] 로 바꿀려면 밑에 처럼 flatten 을 쓰면 된다.

  1. val ints = List(List(1,2,3), List(4,5))
  2. ints.flatten // => this returns List(1,2,3,4,5)

그렇다면 List[List[Int]] 를 List[String] 으로 바꿀려면 어떻해야 할까? flatMap 을 쓰면된다.

  1. scala> val ints = List(List(1,2,3), List(4,5))
  2. ints: List[List[Int]] = List(List(1, 2, 3), List(4, 5))

  3. scala> ints.flatMap(_.map(_.toString))
  4. res0: List[java.lang.String] = List(1, 2, 3, 4, 5)

flatMap은 모나드 이해라든지 함수형에서 매우 중요하므로 아래 2개의 글도 읽어볼것 


여러개의 Future를 반환하는 함수를 List[Future[Item]] 이런식으로 얻는게 아니라, 엮어서 1개의 Future 로 모두 계산될 수 있는 Future[List[Item]]을 얻을 수 있는 거 같은 ~~ 느낌적인 느낌?? 오시나요? 



foreach

val n = List ( 1,2,3,4 )

var sum = 0

n.foreach( sum += _)    //  sum 은 15   * 결과 자체는 Unit이다.


filter

val n = List ( 1,2,3,4 )

n.filter( _ % 2 == 0)    // List (2,4)    * 걸러서 버린다.


partition

val n = List ( 1,2,3,4 )

n.partition( _ % 2 == 0)    // ( List (2,4) , List(1,3) )   * 걸러서 나눈다.


find

val n = List ( 1,2,3,4 )

n.find( _ % 2 == 0)    // Some(2)    * 만족하는 첫번째 원소 반환한다


sortWith

val n = List ( 1,-3,4,2,6 )

n.sortWith ( _ < _ )     // List (-3,1,2,4,5)      *원소를 정렬한


Reducing / Folding / Scanning 

맵이 요소들에 어떤 변화를 가하여 전체 요소들을 개별적으로 바꾸어 주는 역할이라면 
이것은 어떤 요소롤 가득찬 자료구조의 합쳐서 (더하거나 빼거나 등등)  단일 결과를 얻고 싶을때 사용한다. 

reduceLeft

List(1,7,2,9).reduceLeft (_ - _) 

는  ((1 - 7 ) - 2 ) - 9  = -17   이다. 

(_ - _) 여기의 왼쪽 _  이 누적값이고 오른쪽 _ 은 각 요소들이다. 

reduceRight

List(1,7,2,9).reduceRight(_ - _) 는 컬렉션의 마지막에서 시작한다.
즉 1 - ( 7 - ( 2 - 9) ) = -13

(_ - _) 여기의 오른쪽 _  이 누적값이고 왼쪽 _ 은 각 요소들이다. 


foldLeft


folding  은 reducing 하고 비슷하며 , 단지 처음 시작할 원소를 따로 추가해준다.

val numbers = List(1,7,2,9)

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n) res0: Int = 19

0은 시작 값이고 m은 값을 누적하는 변수 역할을 한다. n 은 물론 리스트의 요소이다. 
0 + 1 + 7 + 2 + 9  이고

(0 /: List(1,7,2,9)) (_+_) 이렇게 쓸 수도  있다. 


사실 이런 예제라면  그냥 sum 을 사용하면 된다.






'Scala' 카테고리의 다른 글

스칼라 강좌 (22) - apply 정리  (0) 2016.09.30
스칼라 강좌 (21) - Option 과 Either  (0) 2016.09.29
스칼라 강좌 (19) - implicit  (0) 2016.08.13
스칼라 강좌 (18) -trait  (1) 2016.08.06
스칼라 강좌 (17) - case class  (0) 2016.08.06

+ Recent posts