2018년 8월 7일 화요일

펑터, 어플리커티브 펑터, 모나드에 대해 공부해보자.

출처들:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%82%98%EB%93%9C_(%EB%B2%94%EC%A3%BC%EB%A1%A0)
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%85%B8%EC%9D%B4%EB%93%9C
https://ko.wikipedia.org/wiki/%EB%B2%94%EC%A3%BC%EB%A1%A0
https://en.wikipedia.org/wiki/Monad_(category_theory)
https://en.wikipedia.org/wiki/Monad_(functional_programming)


펑터, 어플리커티브 펑터, 모나드에 대해 공부해보자.



1. 펑터(Functor)란 무엇인가.

박스 안에 당신이 사랑하는 무엇 인가를 가둔다. 박스 안에 있는 물건은 마음대로 변경할 수 없다.
[1]  # [] 가 박스의 형태라고 하자. 
# (어짜피 리스트는 박스의 한 형태이며, 또한 박스처럼 생겼으니 박스라고 하자.)
당신은 박스 안에 1을 넣었다. 나중에 시간이 지나서 이 박스 안에 1에 +1을 하고 싶어졌다고 하자.
당신은 박스 안에 있는 것을 마음대로 빼내서 2로 바꿀 수 없다.
이럴때 필요한 것이 functor이다.
class Functor f where
fmap :: (a -> b) -> f a -> f b

여기서 f는 박스의 형태를 말한다. 위에서 우리는 []가 박스의 한 형태라고 말 한적이 있다.
위에서 a는 당신이 소중하게 여기는 값의 형태이다. 나는 여기에 1 을 넣었으니, Number 일 것이다.

내 박스 안에 값을 더하고 조심스럽게 변화시키고 싶다. 당신은 박스에 내용을 꺼내서 변경할 수 없다.
왜냐하면 그것은 너무 소중해서 바깥 세상에 더럽혀지면 안된다. 대신
박스 안에 함수를 넣는다. 그것이 (a -> b)이다. a형태의 값을 b로 변경하는 함수를 박스 안에 넣는다고 상상해보자.
내가 너무나도 사랑하기에 꺼낼 수는 없지만 변경해야 할 때, 당신은 그 박스 안에 함수를 넣는것이다.

>fmap (+2) [1]
[3]
나는 [1] 에서 1을 꺼내서 2를 더하지는 못했지만, [1] 박스 안에 (+2) 를 넣어서 박스 안에 값을 3으로 바꾼후 박스에 넣어진 채로 리턴받았다.
<$>에 대해서 알아야 할 것이 있는데 <$>는 fmap의 infix버전이라고 보면 된다. (그러니까 줄임말 같은 것인데 infix로 쓰인다는 것)
(+2) <$> [1]
[3]


2. 어플리커티브 펑터

이제 내 안에 있는 박스를 소중하게 변경하는 법을 알았다. 박스안에 값들을 숨기고 (혹은 그 박스 안에 넣어야만 가치있는 값일 지도 모른다. 아니면 효율적인 것일지도) 그 박스 안에 내용들이 더럽혀지지 않도록 하는 것.
그럼으로써 나는 값을 변경하는데 실수하지 않았다는 확신이 들 것이다. 왜냐하면 내가 fmap(펑터)로 박스안에 주입한 함수만이 박스 안의 내용을 변경할 것이니 그것들만 체크하면 되기 때문이다.
(바깥의 내용이 변화를 일으키지 않는다는 확신을 갖게 할 것이다.)

하지만 이렇게 생각해보자. 박스 안에 값들도 중요하지만, 내 소중한 값들을 변경할 녀석(함수)은 중요한 녀석일까 아닐까?
당연히 중요하다.
이녀석들도 박스로 꽁꽁 숨겨서 보관해야할 경우가 있다. 아니면 박스 형태로 올 경우가 있다.
박스 안에 있는 함수를 꺼내서 다른 박스에 넣을 수 있을까? 현재로썬 불가능하다. 그렇기 때문에 펑터를 사용한 것이다. 박스 안에 있는 함수를 꺼낼 수 있다면, 값을 꺼낼 수도 있을 것 아닌가! (왜냐하면 하스켈에서 함수는 값과 별반 다르지 않기 때문에!)
이럴때 어플리커티브 펑터를 이용하는 것이다.
Control.Applicative의 <*>로 가능하게 한다.
>[(+2),(+10)] <*> [10,20,30]
[12,22,32,20,30,40]
앞에 [(+2),(+10)] 는 함수들을 담은 박스다. 이 것을 이용해서 값을 담을 박스 [10,20,30]를 변형시킬 것이다. (아니 변형시키는 것이 아니라 새로 만들어서 리턴하는 것이다.)

다른 예제를 보자.
> (+) <$> [1,2] <*> [10,20,30]
[11,21,31,12,22,32]
<$>는 fmap의 infix버전임을 아까 말했다. <*>는 이제 어플리커티브 펑터라고 보자.
이녀석들은 왼쪽부터 실행된다. 왜냐고?
Prelude> :info <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
-- Defined in ‘Data.Functor’
infixl 4 <$>

Prelude> :info <*>
class Functor f => Applicative (f :: * -> *) where
...
(<*>) :: f (a -> b) -> f a -> f b
...
-- Defined in ‘GHC.Base’
infixl 4 <*>
infixl 4 <*>, <$> 이 보일 것이다. infix는 중위연산자라는 것이고 그 다음에 붙어있는 l은 left인 것으로 알고 있다. 그러니 left부터 인것이다. (4)는 우선순위이다. 누구먼저 실행되냐인데 둘 다 4 이므로
왼쪽에 있는 녀석이 실행될 것이다.

(+) <$> [1,2] 가 어떻게 실행되는 것인지 보자.
Prelude> :t (+) <$> [1,2]
(+) <$> [1,2] :: Num a => [a -> a]
박스 안에 a -> a 가 들어있다. 함수라는 말이다. 아마 [(+1), (+2)] 가 들어있을 것이다. 하지만 볼 수는 없다. Show가 구현되지 않았기 때문이다.
그렇다면 [(+1),(+2)] <*> [10,20,30]과 같아진다.

이런 일을 한번에 해주는 녀석이 있다.
(<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y
liftA2로 한번 실행해보자.
Prelude> liftA2 (+) [1,2] [10,20,30]
[11,21,31,12,22,32]

3. 모나드

지금까지 무엇을 배웠나 생각해보자.
펑터 : fmap 혹은 <$>, 박스로 보호되고 있는 값에 함수를 주입하여 [박스 안에 새로운 값]을 넣는다.
어플리커티브 펑터 : <*> 혹은 listA2, 박스로 보호되고 있는 값과 함수로 [박스 안에 새로운 값]을 넣는다.


모나드는? 이것들과 별반 다른 것이 없을 것이라고 믿어보자.
(사실 잘 모르겠으며, 만약 이 글을 읽어주는 자비심 많은 독자가 있으시다면, 아래 내용은 저의 의식의 흐름으로 적어간 기록이기 때문에 스킵 하시고 내려가세요. 아래 ==END== 로 )
=========================== START ========================
일단 모나드가 뭐냐? 위키를 봐보자.
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%82%98%EB%93%9C_(%EB%B2%94%EC%A3%BC%EB%A1%A0)
범주론에서, 모나드는 내부 함자 범주의 모노이드 대상이다.
모노이드란?
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%85%B8%EC%9D%B4%EB%93%9C
추상대수학에서, 모노이드는 항등원을 갖는, 결합 법칙을 따르는 이항 연산을 갖춘 대수 구조이다.
항등원을 갖고, 결합 법칙을 따르는 이항연산(binary oeprator)을 갖춘 구조인데 항등원은 아무리 연산을 해도 같은 것이고, 결합 법칙은 다르게 결합해도 같은 것을 말한다.
곱하기(*)를 예로들자
15 * 1 = 15 (항등원)
(15 * 2) * 10 = 15 * (2 * 10) (결합법칙)
이런 형태이면 모노이드라고 부르는가 보다.

여기서 범주론은 뭘까?
https://ko.wikipedia.org/wiki/%EB%B2%94%EC%A3%BC%EB%A1%A0
수학에서 범주론(category theory)는 수학적인 구조와 그 사이의 관계를 "범주"라는 추상적 개체를 다루는 이론이다.
20세기 전반에는 수학을 이해하는 도구로 집합론을 꼽았다.
(집합론 : 수학의 이론이란 것은 모두 어떤 대상을 가지고 있는데 이 대상의 모입을 '집합'이라고 부르고 각각의 대상은 이 집합의 원소로 보는 방법)
그러나 이 대상을 이해하려면 '집합'으로 부족함을 깨달았다. '집합 사이의 관계'를 파악해야만 가능하다는 사실을 파악한다.
하여 두 집합 사이의 관계(relation)특히 함수(function)을 써서 이해한다는 집합론을 만들었다. (그것이 범주론인듯... 집합이긴 하지만 관계를 중시한다는 건가)

어떤 구조를 갖는 대상 전체의 모임(위상공간의 모임, 군의 모임, 등등)을 category라 부르고, 두 category 사이에 대응을 functor라고 부른다.
그리고 homology 이론(상동성? 어떤 형질의 진화 과정 동안 보존된 것)을 잘 보면(난 모르지만...) 대응관계(functor)만 알면 이 이론의 결과를 얻게 된다는 것을 알 수 있었다.

즉, category의 여러 성질을 알아내는데 그 성질은 functor가 다 가지고 있다는 것이다. 즉 어떤 집합을 알려 할 때, 이 집합의 요소는 몰라도! 집합에서 나오고 들어가는 함수들 전체의 합성관계만 알아도 집합의 성질을 알 수 있게 된다.
이말이다.

여기서 알 수 있는 것은 functor란 것은 이쪽 집합(세계)에서 저쪽 집합(세계)로 매핑해주는 녀석이라는 것.
https://en.wikipedia.org/wiki/Functor
- In mathematics, a functor is a map between categories.

쓰면서도 뭐가 뭔지 모르겠다. 일단 모나드로 돌아오자. 내부 함자 범주가 대체 뭐야? (찾아보니 함자가 functor의 한국말이다... 하...)
내부 함자 범주가 뭔진 잘 모르겠지만, 모나드에만 이제 집중해서 다시 찾아보자.
https://en.wikipedia.org/wiki/Monad_(category_theory)
In category theory, a branch of mathematics, a monad is an endofunctor (a functor mapping a category to itself), together with two natural transformations.
여기서는 모나드가 endofunctor라는데 괄호안에 있는 것이 뭘 말하는지는 잘 모르겠다. (functor인데 범주 자신에 매핑된다는 걸보니 아마 이런걸 말하는 가보다. f S -> S, 그러니까 이 박스에서 다른 박스 형태로 매핑되는 것이 아니라.
자기자신과 같은 박스형태로 매핑된다는 뜻인가보다... 사실 잘 모르겠다.)

함수형프로그래밍쪽 위키를 봐보자.
https://en.wikipedia.org/wiki/Monad_(functional_programming)
- In functional programming, a monad is a design pattern that defines how functions, operations, inputs, and outputs can be used together
to build generic types, with the following organization:
1. Define a data type, and how values of that data type are combined.
2. Create functions that use the data type, and compose them together into operations, following the rules defined in the first step.
모나드는 함수, 연산자, 인풋, 아웃풋이 제네릭 타입에서 어떻게 이용되는지 '아래와 같은 방식으로' 정의하는 패턴이다.
1. 데이터타입을 정의하고, 데이터타입의 값들이 어떻게 연결되는지 정의한다.
2. 첫번째 스텝에서 정의된 룰을 따라 함수들을 만든다. 그 함수들은 해당 데이터타입을 사용하고, 연산자로 그 둘을 섞는다(구성한다).
잘 모르겠지만, 수학에서의 monad랑 함수형프로그래밍의 모나드랑은 좀 다른 것 같다. 더 읽어보자.
A monad may encapsulate values of a particular data type, creating a new type associated with a specific additional computation,
typically to handle special cases of the type. For example, the simple Maybe monad encapsulates variables which may have a null value,
representing an option type, and automatically ensures that null values are not passed as arguments to functions that cannot handle them,
serving as an alternative programming technique to throwing and catching exceptions when null values arise.
모나드는 특정 값을 캡슐화 한다. (계산을 하고 박스를 생성해서 거기에 넣는다고 생각해보자.) 일반적으로 특별한 타입을 다룰 때 쓰인다고 한다.
예를들어 Maybe monad가 있다. Maybe monad는 null 값이 있을 수도 있는 값을 캡슐화 한다. 하스켈에서 Maybe 타입은 Just a | Nothing 이 두개가 있는데 Nothing이 바로 null 값을 대신한다고 생각하면 된다.
이 Maybe monad는 자동적으로 null 값을 넘기지 않는다. Just null 뭐 이렇게 넘기는 것이 아니라. Nothing이라는 값이 넘어간다. 아예 그 값(null)이 함수에 패스가 되지 않는 것이다. 그러므로 전혀 그 값을 다룰 수 없다.
그러면 일단 코딩을 할 때 그 녀석이 들어오지 않는다고 생각하고 코딩하면 된다. (null이 들어오는 것을 생각하며 코딩하면 더러워진다고 항상 코드가 더러워지지 않는가!!)
이렇게 만듬으로써 예외를 던진다거나 nullpointerException같은거 캐치하는 일은 이제 필요없게 된다.
Another example is the List monad,
where the empty list is a constant value of type List,
and the cons operator binds a plain value as the head of a previous list.
또 다른 예제는 List 모나드라고 한다.
빈 리스트는 List의 상수값이라고 한다. 그리고 cons 연산자는 값을 list의 앞(head)에 붙이는 역할을 한다.

=========================== E N D ========================
여기까지 읽고 한 번 되돌아 보자.
모나드는 그냥 프로그래밍을 할 때 쓰이는, 디자인패턴 중 하나라고 생각하는게 마음에 편할 것 같다.

자 그렇다면 이 Monad를 어떻게 쓰냐. 일단 Monad의 정보를 한번 보자.
Prelude> :info Monad
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
여기서 (>>=)는 bind라고 불리는 녀석인데 이 녀석이 핵심이다. (>> 이녀석은 then 이었나?) 한번 파헤쳐보자.
이 >>= 녀석이 하는 일은 한마디로 말하자면 박스에 있는 값을 함수 안에 넣는다고 생각해보자. *뇌피셜주의* (펑터에서는 박스 안에 함수를 넣었다면 이번엔 반대다! 왜냐하면 이 함수는 박스를 리턴하기 때문)
아래 타입을 보면 더 명확해진다.
(>>=) :: m a -> (a -> m b) -> m b
중간에 있는 (m -> m b)를 봐보자. 박스 안에 있는 값을 다루는데 input 값이 m a가 아니라 a 다.
이 말은 박스 안에 값만을 추출해서 함수의 아규먼트로 들어갔다는 말이다. 사용법을 봐보자.
m a >>= (a -> m b)
[]를 박스라고 해ㄷ보자.
[a] >>= a를 받아 b를 만든다 그리고 박스([])를 만들어서 거기에 b를 넣는다. -> 그러면 m b 즉, [b] 가 리턴될 것이다.
(이렇게, [a] >>= (\x -> ...) -> [b])

여기서 중요한 것은 어떤 타입(어떤 박스가 들어가 있던)이던 그 녀석들을 넣어서 계산하고 뱉어내는 것이다.
Prelude> (Just 2) >>= (\x -> return $ x + 2)
Just 4
Prelude> (Just 2.0) >>= (\x -> return $ x + 2)
Just 4.0
보면 알겠지만, 박스 안에 내용을 가져와서 x에 바인딩(그래서 >>=의 이름이 bind인가보다)되어 새로운 박스에 넣어서 리턴한다. 위에 return의 타입을 보자. a를 가져와서 m a를 뱉어낸다. 새로운 박스를 만들어서 뱉어내는 것이다.

여기까지가 오늘 정리한 내용이다.
틀린 내용이 있으면 수정을 계속 할 것이나...
무엇이 틀린 내용인지도 잘 모르는 멍청이 이기 때문에 안타깝다.

어떤 수학을 공부해야 이런 수학이론을 위키백과로 검색했을 때 나오는 내용들을 이해할 수 있을까?
함께 공부하고 아니면 함께 이걸로 무언가 만들 수 있으면 재미있을 것 같다.
남은 생도 열심히 살자.

2018년 8월 6일 월요일

[책리뷰] 처음배우는 블록체인을 읽고.



이 책은 블록체인에 대한 개론과 구현을 5:5로 나눠서 설명하고 있다.
처음 책의 반은 여러가지 블록체인의 방법들을 설명하고 있으며, 나머지 반은 구현을 하고 있다.
블록체인에 대해 어느정도 개론을 알고 싶고, 구현 또한 어떻게 하고 있는지 알고 싶다면 괜찮은 구성인 것같다.
필요한 용어들을 아주 간결하게 설명해준다. 구현 또한 큰 설명없이 구현을 해나간다.
위에서 설명했듯이 설명이 거창하거나 세세하지 않기 때문에 종사자가 아니라면 이해가 쉽지 않을 수 있다.
그러므로 이 책 한권으로 블록체인을 알 것이라고 생각하면 안된다. 이 책은 블록체인을 알 수 있는 길을 밝혀준다고 생각하면 된다.
세세하지는 않지만 알아야 되는 용어들이 아주 많이 담겨 있기 때문에, 그 용어들을 이용해서 질문을 하거나, 검색을 하거나 할 수 있기 때문에 괜찮은 전략이라고 생각한다.
장점이라고 생각한다.

하지만 구현을 하는 파트에서는 그 점이 단점이라고 생각한다.
어떻게 구현을 하는지 아주 간결하게 보여지면서 빠르게 넘어가기 때문에 보면서 따라하기에는 꽤나 힘들지 않을까 싶다.
하지만 그럼에도 어떻게 구현되고 어떻게 완성되가는지 지켜보는 것은 나쁘지 않은 것 같다. 정말 구현을 하고 싶으면 그것에 집중된 책을 구매하면 될 것이다.

블록체인에 대한 큰 그림을 보고 싶은 분.
블록체인에 대해 알고 있지만 정리가 필요하신 분.
위의 분들에게 추천합니다.
하지만 블록체인에 대해 상세히 알고 싶은 분이라면 위 책만으로는 부족함을 인지하시고 읽어주시면 좋겠습니다.

2018년 7월 14일 토요일

[haskell]하스켈을 접한지 일주일이 되었다.

하스켈을 접한지 일주일

나는 자바 개발자이지만 자바에 대해 제대로 공부하려 하지 않는다. 정확히 말하면 이제 공부를 하다가 관둔 느낌이랄까... 재미가 없다.
왜 재미가 없을까?

첫번째. 자바를 공부하면서 재미있어 하는 사람을 본 적이 없다.


약간 과장을 하였지만 정말 그러하다. 결국에는 슬픔과 좌절(?)로 귀결된다. 내 경험상 적지 않은 고객사이 대부분(전부) JAVA6을 사용하고 있었다.
누가 JAVA8을 배우고 람다를 사용하는가. 처음 자바 학원을 다니면서 JAVA8을 사용하면서 이것저것 해보고 있는 우리들을 보면서 강사는 "절대 안쓴다..." 라고 했던 말이 생각난다.
우리를 무시하면서 했던 말이었다고 그때는 믿었었으나, 지금 생각하니 씁쓸함이었던 것 같다.
분명 쓰는 사람들도 있겠지만 대부분은 쓰지 않을테니...

그런데
왜 그것이 씁쓸했을까?
그것은 개발자가 생각하는 방식을 방해하기 때문이다. 개발자가 생각하는 방식으로 생각하고 싶지만 우리는 JAVA6 안에 굳어간다. JAVA6 안에서 모든 것을 다 만들 수 있다고 하여도 고통스럽다.
우리는 하루하루 기획서를 받고 요구사항을 얻어서 그것을 구.현.하는데 시간을 쓴다. 구현을 하기 위해 더 많은 자유가 있고 선택권이 있다면 우리는 더 편하게 더 즐겁게 노동을 할 것이다.

JAVA8을 신나게 사용하고 업무로 돌아오는 사람들의 표정을 보면, 아니 키보드 소리만 들어도... 화가 느껴진다. 사실 JAVA8이 람다만을 준 것은 아니기에 더 그러할 것이다.

두번째. 누군가에게는 큰 차이가 없어보인다.


사실 큰 차이가 있다. Executors 로 스레드를 가지고 놀다가 parellel 이란 단어를 붙이는 것만으로 뭔가가 되는 것이 보이는 것은 참으로 대단한 일이다.
하지만 자바8에서 주어지는 병렬기능의 뭐 붙이기만 하면 빨라지는 만능도 아니고, Executors에 붙어있는 기능들도 아주 좋아 보인다.
이전에는 대량으로 이메일을 보내는 회사에 있었다. 가끔(?) 문제가 발생하긴 했지만 Executors와 BlockingQueue AtomicInt 같은 것들로 잘 작동했다.

이렇게 되는 것이다. JAVA8로 해야만 버그가 없습니다. 라고 할 수가 없다.
JAVA6으로도 잘 못짜는데, JAVA8로 잘 짤다고?

이렇게 되는 것이다.
기존에 있는 거나 잘써.

세번째. 새로운 거 배울꺼면 뭣하러 JAVA 하나?


이게 가장 큰 것 같다. 나는 이쪽에 속한다. 사실 자바 개발이 싫은 것보다, 자바 웹개발이 좀 그렇다.
Spring과 JSP 로 이루어지는 기능들을 뚜까뚜까 만드는 내 모습은 참으로 공장에서 찍어내는 듯한 생각이 든다.
아마 대부분 사람들이 그러할 것이다.
다들 공장의 부품으로 살아가고 싶지는 않을 것이다.
새로운 것을 하고 싶을 것이다.
내 자신이 변하고 싶을 것이다.

그러니 새로운 언어를 기웃거리며, 새로운 생각을 습득하고, 내 자신이 뭔가 더 많은 것을 수용하고 있다는 확신이 들게 된다.

하스켈 일주일 접한 후기

나는 여기저기 찔러보는 방식으로만 배운다. 이게 내 잘못된 습관인지도 모르지만 "어짜피 안 쓸거 깊게 들어가서 뭐하나 필요할 때 빨리 배우면 되지" 라는 마음으로 기웃거리기만 한다.
하스켈도 기웃거리기 위해서 배우기 시작했다. 하스켈을 설치할 때는 stack을 선택했다. 패키징 매니저인 것 같았다. 사실 뭣도 모르고 찍어서 일단 설치한 건데, 잘 고른 것 같다.

그리고 이맥스를 세팅했다. 코드컴플릿은 안되더라도 이쁘게는 보이게 해야 했다. 코드 자동완성은 없어도 구글신이 있지만, 이쁘지 않으면 보질 않으니까...
첫날 일단 변수의 종류는 빠르게 훑고 함수를 생성하는 법을 익혔다. 별반 차이 없어보였다. (Clojure를 공부했던 경험에서 끈기를 배웠다)
그러다가

Monad에서 좌절했다. 뭐라는 거야.
하나도 모르겠다. 왜 사람들이 Monad에서 막히는지 알았다. 대부분 하스켈을 가르쳐주는 사람들은 그전까지는 "참 쉽죠?" 라는 식으로 가르쳐주다가
Monad로 오자마자 잘난척을 하고 싶은지 수학적 내용으로 얼버무린다.
그런데 이정도 수학적 내용을 모르면 하스켈을 사용할 수 없는 것인가. 하면서 계속 보게 된다.

하루에 한 시간정도 시간을 내는 것으로는 부족함을 느꼈다.

그리고, 아래 내용이 내가 이해한 Monad에 대한 파편이다. (전혀 이게 맞는지 모르겠음...)
Monad는 카테고리 이론에서 나온 개념이다. 카테고리이론은 어떤 집합(세계)에서 다른 집합(세계)를 함수로 연결시킬 수 있다고 믿었다.
더 나아가서, 집합A를 알기 위해서는 그 집합 자체를 몰라도 집합A에서 집합B으로 연결되는 함수를 가지고 있으면 집합A를 알 수 있다고 한다.

뭐 그렇다고 하고, Monad는 사실 저 위 내용보다는 다른 집합을 가두기 위해서 사용하는 것처럼 보인다.
Haskell은 함수형 언어이다. 인풋이 있다면 그에 따른 아웃풋을 만들어줘야 한다. 그 사이에 몰래 시간값 바꾸고 count값 바꾸고 그런 헛다리짓을 하면 안된다.
하지만 바깥세상에서 들어오는 값들은 그 딴게 어디있나 진흙탕에 들어가야 진흙탕에 빠진 사람들을 구해주지 혼자 깨끗할 순 없다. 그래서 만든 것이. 보호막 같은 것이다.

그래서 Monad라는 개념을 만들었다. 값을 가두는 것이다. 예를 들면 많은데, Maybe 나 [] 가 있고 가장 중요한 IO가 있다.
이제 IO에서 가져온 값을 변경 하려면 IO에서 그냥 꺼낼 순 없다.
IO라는 배리어는 아주 강하다. Haskell 세상에서 그냥 바꿀 순 없는 것이다. 바깥세상 내용을 Hakell 세상에서 맘대로 꺼낼 순 없다.

그렇다면 어떻게 해야 하나?

IO라는 배리어 안에 들어가야 한다. 그 일은 하는 녀석은 bind operator이다. ">>=" 로 보이는데 이 녀석을 이용하면 된다.
이 녀석을 사용하면 안에 들어가서 무언가를 할 수 있다. 안에 들어가서 꺼내올 것인가? 내가 보기에는 그럴 수는 없는 것 같다.
한번 배리어 안으로 들어가면 그 값을 가지고 나올 수는 없다. 대신 그 안에서 여러 함수들로 바깥 세상의 값을 변경할 수 있고 그 값을 다시 내보내서 세상에 반영할 수 있다.

e그래서 일단 짬짬이 토이 프로젝트를 만들어봤다. 정말 단순해서 100줄도 안짰다. 그런데 Monad가 이해가 안되서 끙끙댔다.
이것은 내가 필요해서 만든 것인데. 그냥 여러 패스에 파일들을 한번에 각각 다른 패스에 복사해주는 것이다. 바깥세상과 단순히 어떻게 연결이 되나 실험을 해볼 겸 해서 만들어봤다.
https://github.com/ssisksl77/dockeybay

여기까지가 일주일을 바짝 익혀본 소감이다. 이젠 심심할 때마다 구경하면서 하스켈에 대해 더 친해져 봐야겠다. 이 녀석은 정말 재미있다.
다른 사람들하고 같이 공부했으면 좋겠다.

동적설계법 배낭문제 - 2

package algorithm;

import java.util.Arrays;

public class Knapsack {

    static final int MAX_N = 100;
    static int[][] dp = new int[MAX_N][MAX_N];
    static int W;
    static int[] v;
    static int[] w;
    static int n;

    public static void main(String[] args) {

        n = 4;
        w = new int[] {2, 1, 3, 2};
        v = new int[] {3, 2, 4, 2};
        W = 5;
        // Arrays.fill(dp, -1);  error!
        for (int[] row : dp)
            Arrays.fill(row, -1);

        // int res = solve(0,W);
        // System.out.println(res);
        solve2();
    }

    private static int solve(int idx, int weight) {
        if (dp[idx][weight] >= 0) {
            return dp[idx][weight];
        }
        int res;
        if (idx == n) {
            res = 0;
        } else if ( weight < w[idx]) {  // skip
            res = solve(idx+1, weight);
        } else {  // 넣는 경우, 넣지 않는 경우.
            int a = solve(idx + 1, weight);
            int b = solve(idx + 1, weight - w[idx]) + v[idx];  // 이 경우에만 물건을 넣는 경우 v[idx]로 이때만 추가된다.
            res = Math.max(a, b);
        }

        return dp[idx][weight] = res;
    }

    private static void solve2() {  // i=idx, j=weight
        for (int[] row : dp)
            Arrays.fill(row, 0);  // -1이 아님을 주의. 캐시를 하는 것이 아니라. 전부 더할 것이다.

        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j <= W; j++) {
                if (j < w[i]) {
                    dp[i][j] = dp[i + 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
                }
            }
        }

        for (int i = 0; i <= W; i++) {
            for (int j = 0; j <= W; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println(dp[0][W]);
    }
}

동적설계법 - 배낭문제 -1

package algorithm;

import java.util.Arrays;

public class Knapsack {

    static final int MAX_N = 100;
    static int[][] dp = new int[MAX_N][MAX_N];
    static int W;
    static int[] v;
    static int[] w;
    static int n;

    public static void main(String[] args) {

        n = 4;
        w = new int[] {2, 1, 3, 2};
        v = new int[] {3, 2, 4, 2};
        W = 5;
        // Arrays.fill(dp, -1);  error!
        for (int[] row : dp)
            Arrays.fill(row, -1);

        int res = solve(0,W);

        System.out.println(res);

    }

    private static int solve(int idx, int weight) {
        if (dp[idx][weight] >= 0) {
            return dp[idx][weight];
        }
        int res;
        if (idx == n) {
            res = 0;
        } else if ( weight < w[idx]) {  // skip
            res = solve(idx+1, weight);
        } else {  // 넣는 경우, 넣지 않는 경우.
            int a = solve(idx + 1, weight);
            int b = solve(idx + 1, weight - w[idx]) + v[idx];  // 이 경우에만 물건을 넣는 경우 v[idx]로 이때만 추가된다.
            res = Math.max(a, b);
        }

        return dp[idx][weight] = res;
    }
}

동적설계법 - 배낭문제 0

/**
동적설계법
01 배낭문제(Knapsack problem) - 0

n = 4
(w, v) = {(2,3),(1,2),(3,4),(2,2)}
W = 5
ouput : 7

유명한 배낭 문제이다. 일반적인 방법으로 각 물건에 대해 (넣는다/넣지않는다)로 두 가지 상태로 나눠서 탐색한다.

**/
import java.io.*;
import java.lang.*;
class Main {

  static int W;
  static int[] v;
  static int[] w;
  static int n;
  
  public static void main(String[] args) throws IOException {
    //BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

    n = 4;
    w = new int[] {2, 1, 3, 2};
    v = new int[] {3, 2, 4, 2};
    W = 5;

    int res = solve(0,W);
    System.out.println(res);
  }

  
  static int solve(int i, int W) {
    if (i == n) {
      return 0;
    } else if ( W < w[i]) {
      //skip
      return solve(i+1, W);
    } else {
      // 넣지 않는 경우, 넣는 경우 조사.
      int a = solve(i+1, W);
      int b = solve(i + 1, W - w[i]) + v[i];
      return Math.max(a, b);
    }

  }
}

2018년 7월 13일 금요일

부분집합 [C언어]

문제로 풀어보는 알고리즘.
// 부분집합
#include 

void print_arr(int arr[], int arr_len)
{
  int i;
  for (i = 0; i < arr_len; i++)
    printf("%d ", arr[i]);
  printf("\n");
}

/*
부분집합 공식
P(A) = (e*P(A')) U P(A')
e를 포함한 A' 와 e를 포함하지 않는 A'의 합집합
A'는 A' = A - {e} 라고 A에서 집합 {e}를 뺀 것.
그렇지만 코드가 좀 이해가 안된다.
*/
void subset(int set[], int set_size, int n, int index)
{
  if (index == n) {
    print_arr(set, set_size);
    return;
  }
  /*
  set[set_size]가 계속 변경됨.
  index가 올라감에 따라 set[set_size]의 기존 집합이 덮어씌워진다.
  ex) 
  set = [0 1 2] 에서 index가 오르면서
  set = [0 1]
  set = [0 2]
  뭐 이런식으로... 다른 언어였으면 그냥 set같은 걸 사용하면
  좋을 것 같다.
  */
  set[set_size] = index;  // set[size_size]로 집합을 흉내.
  subset(set, set_size+1, n, index+1);  // index(e) 포함한 경우.
  subset(set, set_size, n, index+1);  // index(e)를 포함안한 경우.
}

#define N 100
int main() {
  int set[N], n;
  printf("input n : ");
  scanf("%d", &n);
  subset(set, 0, n, 0);
  return 0;
}

[java] 탐욕알고리즘 연습 - 3

// Fence Repair p.68
/**
널빤지 N개를 자르려고 한다. 필요한 길이는 L1, L2, L3는 배열로 주어진다.
널빤지 N개의 Ln의 길이로 자르려고 할 때 최소 비용을 구하라.
N = 3, L = {8,5,8}
21을 13 과 8로 자르는 비용은 21
13을 5, 8로 자르는 비용 13
총 비용은 34다

탐욕 알고리즘으로 풀 수 있다. 
- 가장 짧은 널빤지 노드와 그 다음으로 짧은 널빤지의 노드는 형제면 된다.

**/
import java.io.*;

class Main {
  public static void main(String[] args) throws IOException {
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int ans = 0, N = 3;
    int[] L = {8, 5, 8};

    // 널빤지가 1개가 될때까지 
    while (N > 1) {
      // 가장 짧은 널빤지 mil1, 다음으로 짧은 널빤지 mil2를 구함.
      int mil1 = 0, mil2 = 1;
      if (L[mil1] > L[mil2]) {
        int tmp = mil1;
        mil1 = mil2;
        mil2 = tmp;
      }

    //3번째 배열부터 또 mil1, mil2보다 작은 것들이 있는지 확인하고 있다면 위치를 갱신한다.
    for (int i = 2; i < N; i++) {
      if (L[i] < L[mil1]) {
        mil2 = mil1;
        mil1 = i;
      }
      else if (L[i] < L[mil2]) {
        mil2 = i;
      }
    }

    // L[mil1] + L[mil2] 병합 (제일 작은 서로를 합침)
    int t = L[mil1] + L[mil2];
    ans += t;
    bw.write("ans : " +  ans + "\n");
    if (mil1 == N - 1) {
      int tmp = mil1;
      mil1 = mil2;
      mil2 = tmp;
    }

    L[mil1] = t;  // mil1을 합친값을 넣어서 커지게 만든다. (합쳐졌으니.)
    L[mil2] = L[N-1];  // mil2는 가장 큰 값을 넣는다. (왜지??)
    N--;
    }

    bw.write("ans : " +  ans + "\n");
    bw.close();
    br.close();
}

탐욕알고리즘 - 2

// 구간 스케줄링 p.59
/**
  n개의 강의가 있다. 각 강의 시간은 si에서 ti까지다.
  가장 많은 강의에 참가하고 싶은 경우, 몇 개의 강의에 참가하는 것이 가능할까?

**/
import java.io.*;
// import java.util.TreeMap;
import java.util.*;

class Main {
  public static void main(String[] args) throws IOException {
    
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    // BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    final int MAX_N = 100000;  // 비교 최대값
    int N = 5;
    int[] S = {1,2,4,6,8};
    int[] T = {3,5,7,9,10};
    Map map = new TreeMap<>();

    for(int i = 0; i < N; i++) {
      map.put(T[i], S[i]); // 종료시간순으로 정렬한다.
    }

    int ans = 0;
    int t = 0;
    for (int i = 0; i < N; i++) {
      if (t < map.get(T[i])) {
        ans++;
        t = T[i];
        bw.write("선택한 강의 : " + (i+1) + "\n");
      }
    }

    bw.write("ans : " +  ans + "\n"); // 출력 3 (1, 3, 5 선택)
    bw.close();
    // br.close();
  }
  public static int min(int a, int b) {
    if (a > b) return b;
    else return a;
  }
}

탐욕알고리즘 - 1

// 코인문제 p.57
/**
  1, 5, 10, 50, 100, 500원짜리가 각각 C1, C5, C10, C50, C100, C500개씩 있다.
  A원을 지불하려면 몇 개의 동전이 있어야 할까. 지불 방법은 적어도 1개는 존재한다고 가정한다.
  **/
import java.io.*;

class Main {
  public static void main(String[] args) throws IOException {
    int ans = 0;
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    // BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    final int[] V = {1,5,10,50,100,500};
    int[] C = {3,2,1,3,0,2};
    int A = 620;

    // 큰 것부터 넣는다. 탐욕알고리즘
    for (int i = 5; i >= 0; i--) {
      int t = min(A / V[i], C[i]); // 
      A -= t * V[i];
      ans += t;
    }
   
    bw.write("ans : " +  ans + "\n"); // 출력 6(500x1, 50x2, 5x2 합계 6개)
    bw.close();
    // br.close();
  }
  public static int min(int a, int b) {
    if (a > b) return b;
    else return a;
  }
}

[python][hackerrank] Repeated String

https://www.hackerrank.com/challenges/repeated-string/problem

strings = input()

# number_of_a = len(''.join(x for x in strings if x == 'a'))
number_of_a = strings.count('a')

n = int(input())

_div, _mod = divmod(n, len(strings))
res = _div * number_of_a

for s, m in zip(strings, range(_mod)):
  if s == 'a':
    res += 1
    
print(res)

python cookbook Strings and Text

String

2.1. Splitting Strings on Any of Multiple Delimiters
>>> line = 'asdf fjdk; afed, fjek,asdf,       fo'
>>> import re
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']

>>> files = re.split(r'(;|,|\s)\s*', line)
>>> files
['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'fo']
>>> values = files[::2]
>>> values
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']
>>> delimiters = files[1::2]
>>> delimiters
[' ', ';', ',', ',', ',']
>>> ''.join(v+d for v,d in zip(values, delimiters))
'asdf fjdk;afed,fjek,asdf,'

>>> re.split(r'(?:,|;|\s)\s*',line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']


2.2 Matching Text at the Start or End of a String
>>> filename = 'peter.txt'
>>> filename.endswith('.txt')
True
>>> filename.startswith('peter')
True

>>> choices = ['http:', 'ftp:']
>>> url = 'http://www.python.org'
>>> url.startswith(tuple(choices))
>>> re.match('http:|https:|ftp', url)
<_sre.SRE_Match object; span=(0, 5), match='http:'>

2.3 Matching Strings Using Shell Wildcard Patterns
>>> from fnmatch import fnmatch, fnmatchcase
>>> names = ['Dat1.csv', 'Dat2.csv', 'config.ini', 'foo.py']
>>> [name for name in names if fnmatch(name, 'Dat*.csv')]
['Dat1.csv', 'Dat2.csv']

>>> fnmatchcase('foo.txt.','*TXT')
False


2.4. Matching and Searching for Text Patterns
>>> from fnmatch import fnmatch, fnmatchcase
>>> names = ['Dat1.csv', 'Dat2.csv', 'config.ini', 'foo.py']
>>> [name for name in names if fnmatch(name, 'Dat*.csv')]
['Dat1.csv', 'Dat2.csv']

>>> fnmatchcase('foo.txt.','*TXT')
False

2018년 5월 15일 화요일

[리뷰] 머신러닝을 간결하게 정리한 [핸드온 머신러닝]을 읽고


이 책은 정말 머신러닝으로 무언가를 해보려는 분들에게 필요한 책입니다.
배우기 위한 책은 아니며 새롭게 익히고 싶은 사람들에게는 다른 책을 찾아보시길 바랍니다.
이 책은 친절한 책이 아니며, 그것을 위한 책또한 아닙니다.

머신러닝을 안다는 가정하게 해결책을 제시하기 위해 만들어진 책처럼 보입니다.
각각 경우에 쓰이는 공식들을 설명하고 어떻게 쓰는 지 설명되어 있습니다.
지극히 간결하여 찾아보기 좋으나, 실제로 이것이 어떤 것을 의미하는 지는 잘 알 수 없어 아쉬웠습니다.
아무리 간결해도 확률에 대한 설명은 좀 더 친절해도 좋지 않았나 싶었습니다.

무언가 막혀 있을 때, 해법을 줄만한 뭔가가 필요할 때. 도움이 될 책입니다.
문제를 해결하기 위한 여러가지 방법을 제시하기 때문입니다.
Python을 이용하고 있지만 사실 Python, 싸이킷런, 이런것들을 설명하고 있다는 점 보다는
어떤 확률공식이 있는지, 어떻게 사용되고 있는지를 알 수 있다는 점에서 높은 점수를 주고 싶습니다.
여러분이 머신러닝을 공부를 어느 정도 하였고, 어떻게 쓰이는 지 알고 싶으신 분들에게 추천합니다.

아직 다 읽지는 못하였지만
읽으면서 느낀 점은 수학공부 확률공부를 해야겠다는 것입니다.