소프트웨어 개발은 집을 짓는것 보다는 정원을 돌보는 일이라고 합니다. ㅡ실용주의 프로그래머 인용


살아 숨쉬는 녀석들을 꾸준히 관리 해 줘야 한 다는 의미겠지요. 더 이쁘고 건강 하게...
가꿀수록 좋아지는 소프트웨어 개발은 정원처럼 한번 만들고 나서 내버려두는것이 아니라, 계속 관심을 쏟는게 필요하며, 그런 의미에서 테스팅 코드, 특히 리팩토링등은 당연해 집니다.

그런것을 무시 하는 관리자들이 많은데요.

개발자들이 코드를 보살펴 줄 시간이 필요 하다고 하면, 말 좀 들으십시요. 다른 정원 만들라고 하지 마시고....모든 정원에 잡초와 폐자재와 쓰레기가 쌓이고 오염되는것을 원치 않는다면 말이지요.

그런의미에서, 개발자들이 어떻게 보살펴 줬는지 질문하고,  확인하며, 어드바이스를 할 줄 모르는 사람이라면 개발자들을 관리하거나 리딩하지마세요. CEO 들은 그런 사람에게 그런 일을 시키지도 마시고~ 약속

참고로 정원을 또 만들때보다 가꿀 수록 실력이 증가합니다. 코드 품질도 좋아지고 직원들 실력도 늘고 굿~


ps. 

6개월 짜리일을 3개월에 하라는 갑과 먹튀 SI 전문가들에게도 꽃이 피길..



저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by [前草] 이승현 (wowlsh93@gmail.com)



진짜 함수형 초보자들을 위한 트레이닝(1) in Scala 


검색해보면 많은 초보자들을 위한 함수형 글들이 있지만, 꽤 어렵지 않나 생각이 드는데요. 

예) 초보자친화적 함수형 프로그래밍 투어 <-  이분 블로그에 좋은 글 많네요.

저도 1년 넘게 회사에서 계속 파이썬만 주력으로 사용해서 감이 많이 떨어져서요. (애초에 감이 있었던적도 없었던..) 이번 글에서는 진짜 쉽게 쉽게 간단한 함수를 사용하는 예제를 통해 감을 익히는 시간을 갖도록 하겠습니다. 덤으로 제네릭을 버무려서 말이죠. 스칼라(Scala) 언어로 할 건데 중간 중간 설명해 드릴테니 몰라도 상관없지 않나 합니다. 그래도 자바,C++,파이썬 같은 언어 경험은 있어야 하긴 할 겁니다.

함수를 매개변수로 넣는것은 C에서도 가능하며 대부분의 언어에서 문제는 없습니다만, 함수형에서는 왜 특별 할까요? 이유는 순수함수이기 때문입니다. 즉 매개변수로 들어가는 함수가 내부에서 어떤 부작용을 일으킬지 걱정을 전혀 안해도 된다는 겁니다. 그 함수가 어떤 값을 리턴하는지만 관심을 집중하면 되거든요. 

즉 함수를 통한 구성을 하는데 있어서 많은 지식과 불안요소가 삭제되며, 아주 큰 시스템이라도 부분 부분을 명확히 이해 가능하기 때문에, 코딩을 하고, 유지보수를 하는데 있어서 심플해 집니다.

자 시작해보죠!!

예1)

def add (a : Int, b : Int): Int = {
a + b
}

스칼라에서 함수는 def 로 시작합니다. 
a : Int 는 매개변수 a 는 Int 타입이라는 의미이구요. 자바와는 순서가 다릅니다. 
리턴 값은 Int 형이라는 뜻이며 
= { } 에 함수의 본문을 넣습니다. 
a+b 를 리턴하네요. 스칼라에서는 return 을 보통 생략합니다.

add(1,2)

이렇게 호출해주면 3이 나올 겁니다.

def add (a : Int, b : Int): Int = a + b

참고로 이렇게 {} 를 생략 할 수도 있습니다. 

def abs(n: Int): Int =
if (n < 0) -n
else n

절대값을 리턴해주는 함수입니다. return 문은 역시 없죠. if 문을 통해 통째로 평가되어진 값이 리턴되니까요.


예2)

예1에서 add 함수는 Int 형 밖에 사용을 못했는데요. Double 형도 같이 사용하고 싶을 때 어떻하죠? 
네 이때 제네릭(타입 파라미터) 이 사용됩니다. Int 라고 못박지 말고, 그냥 A 라고 해 두는거죠. (사실 aaaa 라고 해도 됩니다만 제네릭에서는 보통 T,A,B,C 이렇게 짧게 사용합니다.) 

def add[T](x: T, y: T): T = x + y

자 이렇게 해주는것이 가장 먼저 떠올랐습니다. 그냥 T 라는 타입으로 대표해서, T 에는 Int, Double 아무거나 포함한다는 것 이겠네요? 뭐 이렇게 해서 add(1,2) or add(5.5, 4,3) 해 보면? 

삑!!!! 이 형태는 오류이며  컴파일이 안됩니다. 저 T 라는 미지의 타입이 도대체 + 라는 메소드 (스칼라에서는 모든것이 메소드이며 1,2,3 같은 숫자가 모두 객체입니다. 즉 1.+(2) 처럼 1 객체는 + 메소드를 호출하는 것) 즉 저 T 타입은 + 라는 메소드를 가지고 있는 숫자처럼 행동하는 무엇인가로 한정지어져야 하는데 그렇지 않다는 건데요. 이것은 스칼라의 특성이므로 (이것을 해결 하는 방법은 글 마지막에 추가 해 놓았습니다만지금 단계에서는 알 필요 없구요. ) 일단은 그냥 저렇게 해도 된다고 생각하고 넘어갑니다. 

def square(x:Int):Int = x * x

def square[T <: Number](x : T):T = { x * x }

이렇게 해도 마찬가지로 에러가 납니다. 


예3) 

def doSomthing (a :Int, f : SomeFunction ) : Int = {
...
}

이번에는 두번째 매개변수로 어떤 함수가 들어왔습니다. (슈도코드입니다) 함수 내부에는 어떻게 구현 될 까요?

def doSomthing (a :Int, f : () => Int ) : Int = {
a + f()
} doSomthing(1, ()=>3) // 이렇게 호출 !! 결과는 4

f 함수가 매개변수 없이 작동하는 함수라면 이렇게 구현 될 수도 있을테고~ 뭘 지지고 볶고 하든 리턴으로 Int 형만 내보내면 됩니다. 
여기서 () => 3 는 스칼라에서 사용하는 함수리터럴(람다식)입니다.  매개변수가 없고  => 오른쪽은 함수 본문으로 그냥 3을 리턴한다는 뜻입니다.

def doSomthing (a :Int, f : Int => Int ) : Int = {
f(a)
} doSomthing(3, (x)=>x*x) // 이렇게 호출 !! 결과는 9

f 함수가 매개변수를 하나 넣어야 작동하는 함수라면 이렇게 구현 될 수도 있을겁니다. 역시 Int 형으로 리턴하기만 하면 됩니다. 여기서 (x) => x*x 매개변수 하나를 받아서 제곱해서 리턴한다는 함수리터럴이죠.  

def doSomthing (a :Int, f : Int => Int ) : Int = {
a + f(10)
} doSomthing(3, (x)=>x*x) // 이렇게 호출 !! 결과는 103


물론 이렇게 구현 될 수도 있습니다. 

다시 반복해 말하지만 중요한것은 함수의 파라미터로 어떤 함수가 들어 갈 수도 있다는 것과, 그 함수가 어떤 타입을 매개변수로 받고 리턴받는지에 대해서만 좀 신경 쓰면 된다는 겁니다. 그 함수는 항상 동일한 파라미터를 넣어주면 동일한 값을 내뱉어주며, 내부에서 어떤 변경을 가해서 멀티쓰레딩에 위험요소를 가져올수 있는 어떤 부작용도 일으키지 않는다고 생각하면 함수를 엮어서 사용하는데에 좀 더 자신감과 명백함을 가질 수 있게 되겠지요. 


예4) 

def doSomthing (a :Int, f : Int => Int ) : Int => Int = { x => 
f(x * a * 100)
}

함수객체는 매개변수로만 들어오는것이 아니라 리턴으로도 사용됩니다.
여기서는 Int 를 매개변수로 받고 Int 를 리턴해주는 함수를 리턴해 주고 있습니다. 

(Int => Int) 에 해당하는 x => f(x * a * 100) //매개변수 하나를 받는 함수리터럴을 리턴해줍니다.

매개변수로 받은 f 함수가 Int 를 리턴해주는 함수라는것에 집중해 주세요. 


val func1 = doSomthing(1, (x)=>x*x) //매개변수 하나를 입력받고 Int 형을 리턴하는 함수를 얻음
println(func1(3)) // 답은 90000

이렇게 사용 할 수 있겠네요.


예5) 

def factorial(n: Int): Int = {

def go(n: Int, acc: Int): Int =
if (n <= 0) acc
else go(n-1, n*acc)

go(n, 1)
} factorial(3) // 답은 6 (3 * 2 * 1)

꼬리재귀함수입니다. 잘보면 go 함수를 내부에서 재귀 호출해주는데 가장 마지막에 호출합니다. 그래서 꼬리재귀입니다. somthing * go (...) 이런식으로 되어있다면 꼬리재귀가 아닌데요. 여기서 마지막에 호출되는것은 * 곱하기이니까요. 

def factorial2(n: Int): Int = {
var acc = 1
var i = n
while (i > 0) { acc *= i; i -= 1 }
acc
}

재귀 함수를 안쓰고 while 문을 쓴다면 위와 같을 겁니다. 이렇게 하면 상태를 변화시키는 var 즉 변수가 사용되네요.

예6)

def findFirst[A](as: Array[A], p: A => Boolean): Int = {

def loop(n: Int): Int =
if (n >= as.length) -1
else if (p(as(n))) n
else loop(n + 1)

loop(0)
}
findFirst(Array(1,10,100), (x: Int) => x == 10)  // 답은 1 

배열에서 10 과 같은 요소의 인덱스를 리턴해 주는 함수입니다. 역시 꼬리재귀를 사용했구요. 순수함수입니다. 특히 눈여겨 볼것은 2가지 인데요.

첫째) 

두번째 매개변수의 시그니처가 p: A =>Boolean 이죠? 즉 A 타입을 인자로 받아서 Boolean 타입을 넘겨주는 함수를 인자로 받겠다는 겁니다. 이 조건에 충족한 함수리터럴(익명함수) 을 넘겼습니다. 
(x: Int) => x == 10  말이죠.  x == 10 은 Boolean 타입으로 평가됩니다. 

둘째) 

배열의 타입을 강제 하지 않았네요. 타입파라미터 A 를 사용했습니다. 따라서 

findFirst(Array("john","park","carry"), (x: String) => x == "park")

문자열의 배열에도 사용 할 수 있게 됩니다.


예7)

def partial[A,B,C](a: A, f: (A,B) => C): B => C =
(b: B) => f(a, b)

마지막 예제입니다. 조금 복잡해 보입니다. 타입파라미터가 3종류이고 (A,B,C) 
매개변수는 2개입니다. 하나는 A 타입이고, 하나는 C 타입을 리턴해주는 함수타입 입니다) 
리턴되는 것 역시 함수타입입니다. B 를 받아서 C 를 리턴해주는거네요. 

함수 본문에서 

 (b: B) => f(a, b)

다음과 같이 B타입을 매개변수로 넣고 f(a,b) 를 통해 C 타입이 리턴되므로, 

: B => C 

이렇게 리턴되는것에 충족됩니다. 잠시 머리속으로 굴려보는 시간을 갖겠습니다. 생각해보세요. 
새로운 파라다임을 공부하기 위해서는 반드시 익숙해 져야하므로 반복,반복 밖에 답이 없습니다. 

여기서 partial 이라는 함수는 a 값이 이미 내장된 또다른 함수를 제공해 주는 것이 목적일 겁니다.


부록 :

해당내용을 시작하는 시점에 연구할 필요는 없을거 같습니다. (C++개발자들의 대부분이 템플릿메타프로그래밍이나 템플릿 그 자체도 깊이있게 아는사람이 거의 없듯이.. 뭐 그래도 C++ 개발자잖아요?) 

def square[T <: Number](x : T):T = { x * x }

def add[A](x: A, y: A): A = x + y

자 스칼라에서 이렇게는 컴파일이 안되며 (클래스가 아닌 자바의 primitive로 스칼라의 기본 numeric 타입이 매핑되는데, 즉 자바와의 호환성을 유지시켜주기 위해 어쩔 수 없다고도 하는데, 키 포인트는 스칼라에서는 모든 수가 공유하는 공통의 부모타입이 없으며, 대신 수학 라이브러리에서 지원하는 타입 T 에 대한 목시적인 Numeric[T] 가 있다.) 이것을 해결하기 위한 방법은 아래와 같습니다. 타입클래스 패턴이라고도 하는데요.

Numeric[T] 를 사용하면 아래와 같이 간단히 해결되며,  

def square[T](x: T)(implicit num: Numeric[T]): T = {
import num._
x * x
}

def add[A](x: A, y: A)(implicit num: Numeric[A]): A = {
import num._
x + y
}

직접 구현 해서 해결하는 방법은 아래와 같다.

trait Addable[A] {
def plus(x: A, y: A): A
}

implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) {
def +(rhs: A): A = ev.plus(lhs, rhs)
}


implicit object IntIsAddable extends Addable[Int] {
def plus(x: Int, y: Int): Int = x + y
}


implicit object DoubleIsAddable extends Addable[Double] {
def plus(x: Double, y: Double): Double = x + y
}
def add[A: Addable](x: A, y: A): A = x + y

add(5.4,4.3) or add(2,3)




레퍼런스:

Functional Programming in Scala 
https://typelevel.org/blog/2013/07/07/generic-numeric-programming.html
https://twitter.github.io/scala_school/advanced-types.html



저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by [前草] 이승현 (wowlsh93@gmail.com)



스칼라 강의 (47)  ADT (Algebraic Data Types) 이란? 

위키백과: 대수적 자료형(Algebraic data type)은 다른 자료형의 값을 가지는 자료형이다. 대체로 다른 자료형을 생성자로 감싸고 있다. 어떤 값도 대수적 자료형의 생성자의 인자가 될 수 있다. 반면에 다른 자료형은 생성자를 실행할 수 없으며 형식 일치(Pattern matching) 과정을 통해 생성자를 얻을 수 있다

가장 일반적인 대수적 자료형은 두개의 생성자를 가진 목록형(list)이다. 목록형은 비어있는 목록을 위해 Nil 또는 []를 지원한다. 예를 들어 LISP에서는 Cons(생성자의 준말)나 ::, :등을 이용하여 짧은 목록을 결합하여 새로운 목록을 만들 수 있게 한다. ((Cons 1 '(2 3 4)) 또는 1:[2,3,4]와 같은 방법으로 사용된다

스칼라에서는 대략 trait 와 케이스 클래스로 ADT 를 정의 하는데 , 아래 예를 보자. 

스칼라는 아시다시피 하이브리드 함수형 언어라, 직접적으로 만들 순 없고  먼가 OO 틱하게 만든다.

Option


sealed trait Option[+A]
case object None extends Option[Nothing]
case class Some[A](a: A) extends Option[A]

Either

sealed trait Either[+A, +B]
case class Left[A](a: A) extends Either[A, Nothing]
case class Right[B](b: B) extends Either[Nothing, B]

Try


sealed trait Try[+A]
case class Success[A](a: A) extends Try[A]
case class Failure[A](t: Throwable) extends Try[A]

List


sealed trait List[+A]
case object Nil extends List[Nothing]
case class ::[A](head: A, tail: List[A]) extends List[A]

object List {
def apply[A](as: A*): List[A] = ...
}


아래 자세한 설명이 있으니 각자도생하자. 설명을 딱 부러지게 할 레베루가 안된다. 
다른곳에서 함부러 타입을 추가하지 못하게 제한을 하는 거 봐서 (sealed)  먼가 타입을 만드는데 있어서, 구조화 하고 제한 된 갯수의 타입을 만드는것 같다. (합 혹은 곱만큼) 정확한 타입은 패턴매칭을 통해서 분류 가능하고~
이런 ADT 를 이용해서 함수형 프로그래밍을 위한 복합 구성을 하는 것이고~

http://tpolecat.github.io/presentations/algebraic_types.html#1

https://gleichmann.wordpress.com/2011/01/30/functional-scala-algebraic-datatypes-enumerated-types/




ADT 와 타입클래스는 어떻게 다른가요?  - 스택오버플로우 발췌


ADT (Algebraic Data Types)와 유형 클래스는 서로 다른 문제를 해결하는 완전히 다른 개념입니다.

다음과 같이 ADT는 말 그대로 대수적 데이터 타입의 약자이구요. ADT는 데이터를 구조화하는 데 필요합니다. 스칼라에서 가장 근접한 것은 내가 생각하기에, 케이스 클래스와  sealed traits 의 조합인데 이것은 Haskell에서 도 복잡한 데이터 구조를 구성하는 주요 수단입니다. ADT의 가장 유명한 하스켈 예가 아래에 있습니다.

data Maybe a = Nothing | Just a

이 유형은 Option이라고하는 표준 스칼라 라이브러리에 직접적으로 반영 됩니다.

sealed trait Option[+T]
case class Some[T](value: T) extends Option[T]
case object None extends Option[Nothing]

이것은 Option이 표준 라이브러리에서 정의 된 방법과 정확히 같지 않지만 포인트를 집어줍니다.기본적으로 ADT는 몇 가지 명명 된 튜플 (0-ary, Nothing / None, 1-ary, Just a / Some (value))의 조합입니다.


다음 데이터 타입을 보세요.

-- Haskell
data Tree a = Leaf | Branch a (Tree a) (Tree a)
// Scala
sealed trait Tree[+T]
case object Leaf extends Tree[Nothing]
case class Branch[T](value: T, left: Tree[T], right: Tree[T]) extends Tree[T]

이것은 간단한 이진 트리입니다. 이 두 정의는 기본적으로 다음과 같이 이해 할 수 있는데요. "이진 트리는 리프 또는 분기 중 하나이며, 분기인 경우 어떤 값과 두 개의 다른 트리가 포함됩니다". 이것이 의미하는 바는 Tree 타입의 변수를 가지고 있다면 Leaf 나 Branch 중 하나를 포함 할 수 있으며, 필요하다면 어느 것이 있는지 확인하고 포함 된 데이터를 추출 할 수 있다는 겁니다. 이러한 검사와 추출의 주된 의미는 패턴 매칭입니다.

-- Haskell
showTree :: (Show a) => Tree a -> String
showTree tree = case tree of
  Leaf                    -> "a leaf"
  Branch value left right -> "a branch with value " ++ show value ++ 
                             ", left subtree (" ++ showTree left ++ ")" ++
                             ", right subtree (" ++ showTree right ++ ")"
// Scala
def showTree[T](tree: Tree[T]) = tree match {
  case Leaf => "a leaf"
  case Branch(value, left, right) => s"a branch with value $value, " +
                                     s"left subtree (${showTree(left)}), " +
                                     s"right subtree (${showTree(right)})"
}

이 개념은 매우 간단하지만 매우 강력한데요.

보시다시피 ADT는 닫힙니다. 즉, 타입을 정의한 후에 더 많은 명명된 튜플을 추가 할 수 없습니다. Haskell에서 이것은 구문적으로 시행되며, Scala에서는 다른 파일에서 하위 클래스를  정의하는 것을 허용하지 않는 sealed keyword를 통해 이 작업을 수행합니다.

그러한 이유로 이러한 타입을 algebraic 이라고합니다. 명명된 튜플은 합계(수학적 의미로도)로서 이러한 튜플의 곱 (수학적 의미에서) 및 '조합'으로 간주 될 수 있으며 이러한 생각은 심오한 이론적 의미를 갖습니다.




저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by [前草] 이승현 (wowlsh93@gmail.com)