【手順】

1.ユーザー追加とソフトウェアインストール

1-1.AmazonLINUX2に、以下のコマンドでアップデートしてToolsインストール

sudo yum -y update
sudo yum -y groupinstall "Development Tools"
sudo yum -y install gcc gcc-c++ make git openssl-devel bzip2-devel zlib-devel readline-devel sqlite-devel
sudo yum -y install postgresql-devel
sudo yum -y install gettext

1-1-0.Installing pyenv

1-1-1. django-user配下にpyenvのインストール

su django-user
cd /home/django-user
curl https://pyenv.run | bash

1-1-2. .bashrcへの設定追加

vim .bashrc

以下を追記する

# Setting for pyenv
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

1-1-3. 設定の読み込み

source ~/.bashrc

1-1-4. pyenvを更新

pyenv update

1-1-5. Pythonのインストール、セットアップ

pyenv install -l
pyenv install 3.6.9
pyenv versions
pyenv global 3.6.9
pyenv versions

Pipenvをインストール

1-1-6. pipをアップグレードした後にpipenvをいれる.

pip install --upgrade pip
pip install pipenv
  • ※WSLのpipenv install時になぜかWindows側のpythonが呼ばれる件

やり方はいたってシンプル。installするpythonをバージョン指定ではなく、フルパスで直に指定します

# 【方法1】バージョン指定していれる場合(例:3.7.4)
#  パスはフルパスで指定してください(~/.pyenv...は不可)
pipenv install --python=/home/ユーザー名/.pyenv/versions/3.7.4/bin/python

# 【方法2】globalなpythonを入れる場合(止めておいた方がいい??)
pipenv install --python=$(which python)

# 参考:https://qiita.com/foewhoew32f320/items/bfa90ae1003e45cefe33#

https://medium.com/@jooyunghan/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-fab4e960d263


(번역) 함수형 프로그래밍이란 무엇인가?

Jooyung Han (한주영)
Jan 6, 2016 · 11 min read

이 글은 Kris Jenkins(@krisjenkins)의 “What is Functional Programming?”을 허락을 구해 번역한 것입니다.

Update 2016–01–18 이 글의 2부에 해당하는 “Which Programming Languages Are Functional?”을 번역(“어떤 프로그래밍 언어가 함수형인가?”)하였습니다.


작성일: 2015년 12월 29일

이 글에서 나는 함수형 프로그래밍이 정말로 무엇인지 설명하려고 한다. 일을 제때 끝내야 하는 월급 개발자들 입장에서 수긍할 수 있게 말이다.

다음을 잘 생각해보자. 여러분이 작성하는 모든 함수는 두 종류의 입력과 출력을 가진다.

두 종류라니? 한 종류 뿐이지 않나?

아니, 둘이다. 그것도 확실히 두 종류다. 다음 예제에서 첫번째 종류의 입출력을 살펴보자.

public int square(int x) {
return x * x;
}
// NOTE: 어떤 언어인가는 중요하지 않다. 여기서는
// 강조하기 위해 입출력 타입이 명확한 언어를 골랐을 뿐이다.

여기서 여러분은 int x를 입력으로, int를 출력값의 타입으로 생각할 것이다.

그것이 바로 입력 및 출력의 첫 번째 종류이다. 그냥 일반적인 입출력이라고 불러도 좋다. 이제 입력과 출력의 두 번째 종류를 보여주는 예를 보자.

public void processNext() {
Message message = InboxQueue.popMessage();

if (message != null) {
process(message);
}
}

문법적으로 따지자면, 이 함수는 입력이 없고 어떤 값도 반환하지 않지만, 분명히 무언가 의존성을 가지며, 또 뭔가 하는 일이 있다는 것은 분명하다. 사실은 이 함수가 숨겨진 형태의 입출력을 가진다는 의미이다. 숨겨진 입력은 popMessage()를 호출하기 전의 InboxQueue 상태이고, 숨겨진 출력은 process 호출로 인해 발생하는 모든 것과 모든 일이 끝나고 났을 때의 InboxQueue 상태이다.

실수하지 말자, 분명 InboxQueue의 상태는 이 함수의 입력이다. 그 값을 모르고서는 processNext가 어떻게 동작할지 알 수 없다. 그리고 그것은 진짜 출력이기도 하다. InboxQueue의 바뀐 상태를 고려하지 않고서는 processNext를 호출한 결과를 완전히 이해할 수 없다.

그래서 두 번째 코드 조각에는 숨겨진 입력과 출력이 있다. 무언가를 필요로하고, 또 변경을 초래하기도 하지만 API만 봐서는 추측할 수 없을 것이다.

이 숨겨진 입력과 출력은 공식적인 이름을 가지고 있다. 바로 “부작용(side-effect)”이다. 이 부작용에는 여러가지 종류가 있지만 모두 같은 컨셉으로 아우를 수 있다. “우리가 이 함수를 호출하려면 인수 목록에는 없지만 필요한 것들이 무엇이고, 반환 값에 반영되지 않으면서 하는 일은 무엇인가?”

(사실 나는 용어를 구분해야 한다고 본다. 숨겨진 출력은 “부작용(side-effect)”으로, 숨겨진 입력은 “부원인(side-cause)”으로 말이다. 이 글에서는 간결함을 위해 “부작용”이란 말을 사용하겠지만 분명 부원인도 의미하는 것이다. 나는 모든 숨겨진 입력과 출력에 대해 이야기하고 있다.)

부작용은 복잡성 빙산이다

함수가 부작용(과 부원인)을 가진다면, 여러분은 다음의 함수를 보면…

public boolean processMessage(Channel channel) {...}

… 이 함수가 어떤 일을 할지 안다고 생각하겠지만 그것은 완전히 틀렸다. 함수 내부를 보지 않고 무엇을 필요로 하는지 무슨 일을 하는지 전혀 알 길이 없다. 채널(Channel)에서 메시지를 꺼내어 처리하는 걸까? 아마도. 어떤 조건 하에서는 채널을 닫아버리나? 그럴지도 모른다. 어디 다른 데이터베이스의 특정 카운트를 업데이트하나? 어쩌면. 로깅 디렉토리 경로를 찾을 수 없는 경우에 죽어버리는 건 아닐까? 그럴 수도 있다.

부작용은 복잡성 빙산이다. 여러분은 함수의 Signature와 이름을 보면, 그 함수가 무언인지 알 수 있다고 생각한다. 그러나 함수 Signature의 표면 아래 숨겨진 것은 그 무엇이든 될 수 있다. 어떤 요구 사항이든 숨겨져 있을 수 있고, 또 어떤 숨겨진 변경도 발생할 수 있다. 그 구현을 보지 않고, 여러분은 정말 어떤 것들이 연관되어 있을지 전혀 알 수 없다. API의 표면 아래에는 잠재적으로 엄청나게 큰 복잡성이 숨어 있다. 여러분이 함수를 제대로 파악하려고 할 때 가능한 대안은 세 가지가 있다. 함수 정의를 파고 들거나, 복잡성을 표면 위로 드러내거나, 그냥 무시하고 잘 되길 바라는 것이다. 하지만 결국에는 무시하는 것이 엄청난 실수가 된다.

그래서 캡슐화를 하는 것 아닌가?

아니.

캡슐화는 구현 세부 사항을 숨기는 것에 관한 것이다. 코드의 내부를 숨겨서 호출하는 쪽에서 걱정할 필요가 없게 하는 것이다. 좋은 원칙이기는 하지만 지금 우리가 이야기하는 것과는 다른 이야기다.

부작용은 “구현 세부 사항 숨기기”에 관한 것이 아니다. 코드와 외부 세상과의 관계를 숨기는 것에 대한 것이다. 부원인을 가지는 함수는 그것이 의존하고 있는 외부 요인에 대한 문서화되지 않은 가정을 가진다. 부작용을 가지는 함수는 그것이 바꾸게 될 외부 요인에 대한 문서화되지 않은 가정을 가진다.

부작용이 나쁜가?

부작용이 원래 작성한 프로그래머가 예상한 그대로 정확하게 동작한다면 괜찮을 것이다. 그러나 문제가 있다. 우리는 원래 프로그래머의 숨겨진 예상이 정확하다고, 그리고 시간이 지나도 여전히 정확할 것이라고 신뢰해야만 한다.

이 함수가 작성될 때 기대했던 것과 똑같이 세상의 상태를 셋업했는가? 혹시 어딘가를 바꾸지는 않았던가? 아마 겉으로 봐서는 전혀 연관없어 보이는 코드 조각을 수정했을지 모른다. 아니면 새로운 환경에 소프트웨어를 설치하고 있기 때문일지도 모른다. 세계의 상태에 대한 숨겨진 가정이 있다는 것은 충분히 비슷하니 잘 동작할 것이라는 우리의 숨겨진 희망을 의미한다.

이러한 코드를 테스트 할 수 있나? 이런 코드는 완전히 분리하여 테스트 할 수 없다. 회로 기판처럼 입력을 연결하고 출력만 확인할 수 있는게 아니다. 우리는 코드를 열어보고 숨겨진 원인과 결과를 파악하고, 세상을 그럴듯하게 시뮬레이션해야 한다. 나는 TDD 개발자들이 블랙 박스로 테스트할지 화이트 박스로 테스트할지 헷갈려하는 경우를 여러번 봤다. 그 대답은, 블랙 박스 테스트를 해야 한다이다. 여러분은 구현 세부 사항을 무시 할 수 있어야 한다. 하지만 여러분이 부작용을 허용하게 되면 블랙 박스 테스트를 할 수 없다. 부작용은 블랙 박스 테스트 여지를 없애버린다. 박스를 열고 그 안에 무엇이 들어 있는지 확인하지 않고서는 입력과 출력을 결정할 수 없기 때문이다.

이 효과는 디버깅 시에 증폭된다. 함수가 부작용 (또는 부원인)을 허용하지 않는 경우, 당신은 단지 몇 가지 입력에 대해 출력을 확인하여 올바른지 여부를 알 수 있다. 그러나 부작용이 있는 함수라면? 여러분이 시스템의 다른 부분을 어디까지 고려해야 할지 그 끝을 알 수 없다. 함수가 무엇에든 의존할 수 있고 무엇이든 변경할 수 있다면 버그는 어느 곳에든 있을 수 있다.

우리는 항상 부작용을 표면으로 드러낼 수 있다

이러한 복잡성에 대해 우리가 할 수 있는 일이 있을까? 있다. 사실 시작하기는 매우 간단하다. 함수가 어떤 입력을 가진다면 그렇게 말하면 된다. 출력으로 뭔가를 반환한다면 그렇게 선언하면 된다. 그렇게 단순하다.

예제로 직접 해보자. 아래 함수는 숨겨진 입력을 가진다. 당신이 빨리 찾을 수 있다면 보너스 점수를 주겠다.

public Program getCurrentProgram(TVGuide guide, int channel) {
Schedule schedule = guide.getSchedule(channel);

Program current = schedule.programAt(new Date());

return current;
}

이 함수는 현재 시간(new Date())을 숨겨진 입력으로 가진다. 우리는 이 추가 입력을 정직하게 대함으로서 복잡성을 표면화할 수 있다.

public Program getProgramAt(TVGuide guide, int channel, Date when) {
Schedule schedule = guide.getSchedule(channel);

Program program = schedule.programAt(when);

return program;
}

이 함수는 이제 숨겨진 입력이나 출력이 없다.

이 새로운 버전의 장단점을 살펴 보자.

단점

더 복잡해 보인다. 두 개가 아닌 세 개의 인자를 가진다.

장점

더 복잡하지 않다. 의존성을 숨긴다고 더 간단해지지는 않는다. 의존성을 정직하게 드러낸다고 더 복잡해지지는 않는다.

훨씬 테스트하기 쉽다. 하루 중 어느 때든, 시차 변경이나 윤년을 테스트 하는 경우에도 원하는 시간을 넘겨주기만 하면 되므로 모두 간단하다. 나는 첫번째 버전의 코드가 실제 제품에 사용된 것을 본적이 있는데, 테스트를 위해 시스템 시간을 바꾸느라 별의별 트릭을 동원해야 했다. 인자로 바꿀 수만 있다면 필요한 노력이 얼마나 될지 상상해보라.

추론하기 더 쉽다. 이 함수는 단지 입력과 출력의 관계를 기술하고 있을뿐이다. 여러분이 입력을 알고 있다면 결과가 무엇이어야 하는지 모든 것을 알고 있다. 이것은 정말 대단한 것이다. 우리는 이 코드를 따로 떼어내어 확인할 수 있다. 입력과 출력 사이의 관계만 테스트하면 함수 전체를 테스트한 것이 된다.

(게다가 부가적으로 더 유용한 함수가 되었다. “한 시간 뒤에 시작하는 프로그램이 무엇인가?”를 구하는 코드도 덤으로 얻었다.)

‘순수 함수’는 무엇인가?

두구두구두구…

이제 숨겨진 입력과 출력을 알게 되었으니 “월급 개발자의 순수 함수 정의”를 알려줄 수 있겠다.

모든 입력이 입력으로 선언되고 (숨겨진 것이 없어야 한다) 마찬가지로 모든 출력이 출력으로 선언된 함수를 ‘순수(pure)’하다고 부른다.

반대로 숨겨진 입력이나 출력이 있는 경우는 순수하지 않은 것이며, 함수가 제공한다고 보이는 계약(contract)이 사실은 전체의 절반을 이야기해 줄 뿐이다. 복잡성 빙산이 나타난다. 순수하지 않은 코드를 ‘독립적으로’ 사용할 수는 없다. 독립적으로 테스트 할 수 없다. 테스트하거나 디버그가 필요할 때면 그것이 의존하고 있는 것을 항상 신경써야 한다.

‘함수형 프로그래밍’이란 무엇인가?

순수/비순수 함수를 알게 되었으니 이제 여러분에게 “월급 개발자의 함수형 프로그래밍 정의”를 알려주겠다.

함수형 프로그래밍은 순수 함수를 작성하는 것, 그러니까 숨겨진 입력이나 출력을 최대한 제거하여 가능한한 우리 코드의 대부분이 단지 입력과 출력의 관계를 기술하게끔 하는 것을 말한다.

부작용을 완전히 피할 수는 없다. 대부분의 프로그램은 반환 값을 얻기 위해서가 아니라 어떤 동작을 하기 위해 실행하기 때문이다. 하지만 프로그램 내부에서는 엄격하게 통제하고자 한다. 우리는 가능한 모든 곳에서 부작용(과 부원인)을 제거하고, 또 제거할 수 없는 경우에는 철저하게 통제할 것이다.

다르게 말하자면, 코드 조각이 필요로 하는 것과 유발하게 될 결과를 숨기지 말자. 코드 조각이 제대로 실행하기 위해 뭔가를 필요로 한다면 그렇게 말하자. 뭔가 유용한 일을 한다면 출력 형태로 선언하자. 이렇게 한다면 우리의 코드는 더 명확해 질 것이다. 복잡성이 표면에 드러나고 우리는 그것을 분해하여 처리할 수 있을 것이다.

‘함수형 프로그래밍 언어’는 무엇인가?

모든 언어는 순수 함수를 지원한다. add(x, y)를 순수하지 않게 만들기는 어렵다.(1) 그리고 많은 경우 순수하지 않은 함수를 순수하게 만들때 필요한 일은 모든 입력 및 출력을 함수 Signature에 올리는 것 뿐이다.그럼 모든 프로그래밍 언어가 ‘함수형’인가?

아니다. 만약 그렇다면 굳이 용어를 둘 필요도 없을 것이다.

그럼 “월급 개발자의 함수형 프로그래밍 언어 정의”가 무엇일까?

함수형 프로그래밍 언어는 부작용없는 프로그래밍을 지원하고 장려하는 언어이다.

더 구체적으로 말하자면, 함수형 언어는 여러분이 가능한한 부작용을 제거하고 그렇지 않은 곳에는 철저히 제어 할 수 있도록 적극적으로 도와주는 언어이다.

더 극적으로 표현하자면, 함수형 언어는 더 적극적이고 더 격렬하게 부작용에 적대적인 언어이다. 부작용은 복잡성이고, 복잡성은 버그이며, 버그는 악마이다. 함수형 언어는 여러분들도 부작용에 적대적이 되도록 도와줄 것이다. 여러분과 함께 그들(부작용,복잡성,버그)을 깨부시고 굴복시킬 것이다.

그게 다야?

그렇다. 여러분이 숨겨진 입력이라고는 도저히 생각하지도 못했을 두어가지 미묘한 것들이 있기는 하지만 그것이 본질이다. 그러나 “부작용이 첫 번째 적이다”라는 관점으로 소프트웨어를 개발하기 시작한다면 여러분이 프로그래밍에 대해 알고 있던 모든 것이 달라진다. 이 글의 2부에서는 부작용과 함수형 프로그래밍에 대한 인식을 바탕으로, 프로그래밍이라는 땅 위에 샷건을 쏘아볼 예정이다.

감사의 글

이 포스팅은 함수형 프로그래밍의 본질에 대해 나눴던 논의들에서 출발한 것이다. 특히 “적절한 라이브러리의 도움이 있다면 자바 스크립트를 함수형 프로그래밍 언어로 간주할 수 있는가”에 관한 Sleepyfox과의 대화가 주효했다. 나의 본능적 대답은 ‘아니다’였지만 를 거듭 고민한 끝에 유용한 사고 연결 고리를 따라갈 수 있었다.

제임스 헨더슨에게도 도움을 받았다. 나는 그와 함께 올 한해동안 함수형 프로그래밍과 관련한 여러가지 유익한 아이디어들을 주고 받았다.

말콤 스파크 , 이반 Uemlianin , 조엘 클레르몽 , 케이티 모에, 그리고 내 이름과 발음이 같은 도플 갱어 크리스(Chris) 젠킨스의 교정과 제안에도 감사한다.


함수형 프로그래밍?(Functional Programming)

Functional Programming(함수형 프로그래밍)

지난 토요일에 T 회사 인턴 면접을 보고 왔다. 해당 회사 모바일 개발 직군에 인턴으로 지원하기도 했고, 전 회사에서 하던 것이 Android라서 최근에 구글이 선택한 Kotlin을 공부하기 시작했다. 면접에서 요즘에 공부하는 것이 무엇이냐는 질문에, Kotlin을 막 공부하기 시작했다고 대답했다. 그러자 면접관께서 Kotlin, Swift 등 최근 함수형 언어(Functional Language)가 뜨고 있는데 그 이유를 알고 있느냐고 질문하셨다. Kotlin 공부를 하게 된 이유는 함수형 언어이기 때문이 아니라, 단순히 구글이 Android 언어로 Kotlin을 선택했기 때문이다.

그래서 질문에 대답하지 못했다. 하지만 아는 것이 힘이니 오늘 그 이유에 대해 알아보고자 한다.

함수형 프로그래밍이란(About Functional Programming)

함수형 프로그래밍 은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다. 명령형 프로그래밍(Java 등)에서는 상태를 바꾸는 것을 강조하는 것과 달리, 함수형 프로그래밍은 함수의 응용을 강조한다. 대부분 람다 대수에 근간을 두고 있으며, 다수의 함수형 언어는 람다 연산을 발전시킨 것으로 볼 수 있다.

PURE FUNCTION(순수 함수)

순수 함수 란, Side Effect가 없는 함수를 뜻한다. 즉, 함수 실행이 외부에 영향을 끼치지 않는 함수를 뜻한다. 이러한 성질 덕분에 함수형 언어는 Thread-Safe하고 Concurrency를 지원한다.

ANONYMOUS FUNCTION(익명 함수)

익명 함수 란, 이름이 없는 함수를 뜻한다. 전통적인 명령형 언어에서는 모든 함수에 이름이 부여되어야만 했다. 하지만 람다식을 통해 이름없는 함수를 수행할 수 있다.

유의할 점은 익명 함수의 경우 최신 명령형 언어에서도 지원한다. Java의 경우 Java8부터, C++의 경우 C++11 부터, .NET Framework의 경우 LINQ가 추가된 3.5부터 가능하게 되었다.

HIGHER-ORDER FUNCTION(고계 함수)

고계 함수 란, 함수를 다루는 함수를 뜻한다. 함수형 언어에서는 함수 자체도 ‘값(value)’ 으로 취급한다. 정수를 함수의 인자로 전달할 수 있듯이 어떤 함수도 다른 함수의 인자로 전달할 수 있다. 마찬가지로 함수의 결과 값으로 정수를 반환할 수 있듯이 함수를 반환할 수 있다.

참고 링크


함수형 프로그래밍이 뜨는 이유(The Reason Why Functional Programming is On The Rise)

그렇다면 함수형 프로그래밍이 뜨는 이유는 뭘까? 또 왜 여태까지 뜨지 않았던 것일까?

뜨는 이유

함수형 프로그래밍이 뜨는 이유는 세 가지로 볼 수 있다고 한다.

  • Multi-Core & Concurrency
    • Multi-Core CPU를 장착한 컴퓨터가 대세가 되면서 Multi-Threading을 지원하는 소프트웨어가 필요하게 되었다.
    • 무어의 법칙을 따라 발전하던 CPU가 현실적인 한계에 봉착하자, 수직적 전략을 포기하고 여러 개의 칩이 병렬적으로 동작하도록 만드는 수평적 전략을 선택함.
    • Concurrency를 지원하게 되면서 변경 가능한 데이터(Mutable Data)는 개발자에게 어려움을 가져다 주었다.
    • 함수형 언어의 특징은 데이터가 변경 불가능(Immutable Data)이다. 때문에 Concurrency를 지원하기에 적합하다.
  • 코드의 간결함
    • 객체지향(OOP)를 오랜 시간 사용해온 프로그래머라면 각종 디자인 패턴에 친숙할 것이지만, 함수형 프로그래밍의 관점에서는 디자인 패턴은 우격다짐이다.
    • 필요한 논리 부분과 추상 부분을 구별해내고 패턴에 맞게 구성하는 것은 부수적이고 장식적인 코드가 많아지게 한다.
    • 함수형 프로그래밍의 중요 개념인 Curry, Partial Application, Monad와 같은 기법이 간결하고 우아한 함수의 구성(Composition)을 가능하게 해준다.
  • C#과 Java 같은 기존 언어의 관리모드
    • 기존 언어들이 발전모드에서 관리모드로 접어듦에 따라 프로그래밍 언어와 관련한 새로운 패러다임이 요구되는 현실과 관련이 있다.
    • JVM이나 .NET 플랫폼을 대상으로 여러 언어들이 실험되고 있으며, 실제로 많은 지원이 이루어지고 있다. 때문에 함수형 패러다임은 객체지향 패러다임의 뒤를 이을 새로운 패러다임을 발견하기 위한 전반적인 실험과 암중모색의 와중에 하나의 강력한 대안으로 주목받고 있다.

참고 링크

뜨지 못한 이유

뜨지 못한 이유는 함수형 프로그래밍이 가지는 장점 자체가 단점이기 떄문이라고 한다.

  • Multi-Core & Concurrency
    • Immutable Data Structure는 Thread-Safe를 쉽게 보장하는 대신 ‘오래된’ 데이터를 사용하게 될 수도 있다.
    • Mutable Data Structure는 항상 최신 데이터를 다룬다는 장점이 있지만, Data Consistency를 보장하기 위해 복잡함을 수반한다.
    • 둘 중 어느 것이 낫다고 볼 수 없다.
  • Non-State, No-Side Effect
    • 함수형 프로그래밍은 상태(State)를 배제하여 Side Effect가 없게 동작한다.
    • 하지만 사용자(User)와의 상호작용(Interaction)은 대부분 상태 변화로 모델링된다.
    • 즉, 상태가 없는 함수형 프로그래밍의 한계다.
  • 코드의 간결함
    • 코드가 간결해질 수 있지만, 간결한 코드를 읽기 위해서 학습이 필요하다.
  • 생산성이 올라간다.
    • 함수형 프로그래밍 스타일로 작성할 수 있는 프로그래머를 채용하는데 드는 비용을 상쇄시킬만큼 생산성이 올라야만 한다.
  • 해결할 문제들
    • 세상에는 함수형 프로그래밍 스타일에 적합한 문제도 많지만, 적합하지 않은 문제들도 충분히 많다.

참고 링크


How About Kotlin

Kotlin은 JVM 호환 언어라고 한다. 즉, Kotlin으로 작성된 소스 코드를 컴파일하면 Java Bytecode(.class file)가 생성된다. 아마 Java를 활용하는 모든 곳에 Kotlin을 사용할 수 있을 것이라고 생각된다.

아마 이런 이유 때문에 구글이 Kotlin을 Android 공식 언어로 선택하지 않았나 싶다.

다만 Java의 경우 명령형 언어를 대표하고 Kotlin은 함수형 언어인 만큼, 프로그래밍 패러다임적인 문제가 없을지 궁금하다. 단순히 JVM 위에서 동작한다고 Kotlin을 선택했을까? JVM을 지원하는 언어는 무수히 많은데…


Function Programming 을 공부 하다보면 1급 객체란 말을 많이 언급됩니다. 그래서 1급 객체 (First-class citizen)란 무엇인가를 공부도하고 정리해 공유 하고자 합니다.

1급 객체란?

아래 3 가지조건을 충족한다면 1급 객체라고 할수 있습니다.  

  1. 변수나 데이타에 할당 할 수 있어야 한다.
  2. 객체의 인자로 넘길 수 있어야 한다.
  3. 객체의 리턴값으로 리턴 할수 있어야 한다.
     
    Java와 Kotlin의 비교를 통해 Kotlin 의 함수는 왜 1급 객체고 Java의 함수는 1급 객체가 아닌지 알아보겠습니다.

변수나 데이타에 할당 할 수 있어야 한다.

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val a = test
    }

    val test: () -> Unit = { println("kotlin") }
}
public class java {

    public static void test(){
        System.out.println("java");
    }

    public static void main(String[] args) {
        System.out.println("java");
//        Object a = test;
    }
}

kotlin은 a 에 type이 () -> Unit 인 test 함수 할당이 가능하지만, Java는 불가능 합니다.

객체의 인자로 넘길 수 있어야 한다.

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        function(test)
    }

    fun function(f: () -> Unit) {
       f.invoke()
    }

    val test: () -> Unit = { println("kotlin") }
}

kotlin 은 function 함수의 인자로 함수타입을 전달 할 수 있습니다. 하지만 Java에서는 불가능 합니다.

객체의 리턴값으로 리턴 할 수 있어야 한다.


object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        function()
    }

    fun function(): () -> Unit {
        return { println("kotlin") }
    }

}

function함수는 { println(“kotlin”) }, 즉 함수타입을 반환 합니다.
kotlin에서 함수는 변수나 data에 할당이 가능하며, 함수의 인자로 전달가능 하고, 함수의 리턴값으로도 사용 할 수 있습니다. 그렇기 때문에 kotlin의 함수는 1급 객체라고 할 수 있습니다. 반면 Java의 함수는 위 조건들을 만족하지 못하기 때문에 1급 객체라고 할수 없습니다.

https://steemit.com/kr/@endiyou/4-3


[블록체인] 해시함수 이해 4 - 특성 3 - 퍼즐 게임을 만들 수 있다==코인 마이닝 문제 출제

 in kr •  2 年前  (edited)

블록체인에 사용되는 해시함수는 무엇이고 어떤 특성을 가지고 있는지 알아보고 있습니다.
그 네번째 마지막 글로 해시 함수의 특성 3 - 퍼즐 게임을 만들 수 있다입니다.

이전 글은 아래 링크에서 보실 수 있습니다.
[블록체인]해시함수 이해1 - 해시함수란?
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.
[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다

이글에서 말하는 해시 퍼즐 게임이 비트코인 채굴자(마이너)들이 푸는 문제와 같은 종류입니다.
흔히 암호를 푼다고 하는데요.
알고보면 암호를 푼다기 보다는 랜덤하게 값을 입력해서 원하는 해시가 나오는지 확인하는 작업을 반복하는 겁니다.

모래밭에서 금조각 찾기라고 할 수 있는데요.

금속 탐지기나 심지어는 사금 채취하듯 물에 흘려보내면서 찾는 등 어떤 기술 도입을 전혀 할 수 없다는거죠.
017550-150ppp.jpg

Designed by Freepik

문제는 이런식입니다.

256 bits 결과를 내는 SHA256 해시의 결과가 아래와 같은 입력값을 찾으시오.
해시 : '38744961f4eda323853183a6f6bc135434347285158efaaba7b9280fdb569a95'

해답을 찾는 지름길이 없다

해시의 특성중 충돌이 일어나지 않는다와 해시 결과로 입력을 원본을 알 수 없다는 것이 있습니다.
예를들어 숫자 256으로 나누어서 나머지 값을 해시 결과로 갖는 해시함수가 있다고 하죠.
해시 결과 10일 입력을 찾으라는 문제를 내면 어떻게 하시겠어요?

256 + 10
256 X 2 + 10
256 X 3 + 10
....

아주 간단한 공식으로 바로 해결이 됩니다.
이런 해시는 사용불가입니다.
여기서 예로들고 있는 SHA256은 아직까지는 이런 지름길이 없는 것으로 알려져 있습니다.
무조건 어떤 값을 넣어서 해시를 계산해 보고 해답과 일치하는 지를 봐야하는 거죠.

그래도 뭔가 꼼수를 부릴 수 있지 않을까?

가능합니다.
해시 결과를 미리 계산해서 저장해 두는 데이터 베이스를 만드는 거죠.
일단 최대한 많이 만들고, 계속 만들어 둡니다.
그리고 문제로 출제되는 해시와 이미 저장된 것과 일치되는 것이 나오면 계산할 필요도 없이 바로 답을 알게되는 거죠.

이를 방지하기 위해서 실제 어플리케이션에는 좀 더 다른 방법으로 문제를 출제합니다.
랜덤하게 생성된 키값을 주고요.
이 키와 어떤 값(X)를 붙여서 해시 입력으로 넣었을 때 해시 결과가 아래와 같이 나오는 X를 찾으시오.
예를들면

SHA256 해시의 입력으로 아래 주어진 키에 X를 붙여서 넣었을 때 아래 주어진 해시를 출력하는 X를 찾으시오.
키 : '0c62f876ef1dea830de9f32c2f4b46dd6d74d50d15896e09ef5a2fcd4ac7e1d7'
해시 : '38744961f4eda323853183a6f6bc135434347285158efaaba7b9280fdb569a95'

라고 문제를 출제합니다.

해시의 충돌은 발생할 수 밖에 없습니다.
즉 같은 해시를 만들어 낼 수 있는 입력은 아주 많이 존재한다는 거죠.
하지만 랜덤으로 선택된 키를 제공함으로서 내가 미리 계산해둔 해시 입력값은 의미가 없어집니다.
정답이 미리 계산해 둔 데이터베이스에 있을 수도 있지만, 역시 하늘에 별따기죠.

문제의 난이도 조정은 어떻게 하지?

그런데, 위에 예를 든 것처럼 문제를 출제하면 어떤 경우에는 평생 아무도 못 푸는 문제가 되고 맙니다.
해시의 특성 2에서 40조의 8 제곱에 관한 설명을 보셨죠?
문제의 난이도를 조정하는 법은 의외로 쉽습니다.
아래와 같이 하는 거죠.

256 bits 결과를 내는 SHA256 해시의 결과가 아래와 같은 입력값을 찾으시오.
단, X는 아무 값이나 들어가 일치하지 않아도 됩니다.
해시 : 'XXXXXXXXXXeda323853183a6f6bc135434347285158efaaba7b9280fdb569a95'

해시 결과를 하나의 값으로 고정하는 것이 아니라 특정 그룹으로 묶어 문제를 출제하는 것입니다.
제일 쉬운 문제는 마지막 한 bit만 일치하면 되는 문제겠죠.
확율적으로 2번만 시도하면 1번은 정답을 맞출 수 있습니다.
제일 어려운 문제는 당연히 256 bits가 모두 일치하는 문제고요.

지금까지 블록체인에서 사용되는 해시함수의 특징에 대해서 간단히, 가능하면 쉽게 알아봤습니다.

이전 글은 아래 링크에서 보실 수 있습니다.
[블록체인]해시함수 이해1 - 해시함수란?
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.
[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다
[블록체인] 해시함수 이해 4 - 특성 3 - 퍼즐 게임을 만들 수 있다(현재글)


https://steemit.com/kr/@endiyou/3-2


[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다

 in kr •  2 年前  (edited)

블록체인에 사용되는 해시함수는 무엇이고 어떤 특성을 가지고 있는지 알아보고 있습니다.
그 세번째 글로 해시 함수의 특성 2 - 원본 내용을 알 수 없다입니다.

이전 글은 아래 링크에서 보실 수 있습니다.
[블록체인]해시함수 이해1 - 해시함수란?
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.

첫번째 글에서 해시함수란

어떤 길이의 데이터를 입력해도 정해진 길이의 결과를 주는 함수

라고 했습니다.
143.jpg

Designed by Freepik

이번 해시함수의 특징은 이 해시결과를 보고서는 원본 메시지가 무엇인지 알아내는 것이 거의 불가능하다는 것입니다.
바로 앞의 글에서 해시 함수는 입력 데이터가 다르면 해시 결과가 다르다라고 말했습니다.
같은 해시결과를 만드는 값은 반드시 존재하지만 실제로 그 값을 찾는 것은 불가능에 가깝다고 설명했습니다.
다시 말하면 해시 결과를 알려줘도 그 결과를 만드는 입력 데이터를 찾는 일은 불가능하다는 거죠.

원본을 알아내는 것이 불가능할까? 동전 던지기 문제

동전을 던저 앞(head), 뒤(tail)을 맞추는 게임을 생각해 보겠습니다.
A와 B가 미리 앞, 뒤를 선택해서 심판을 보기로한 C에게 제출합니다.
이때 그냥 제출하는 것이 아니라 SHA256 해시를 돌려서 결과를 제출합니다.
심판 C가 실제 동전을 던진 후에 A, B가 제출한 답과 비교를 하려고 합니다.
제출된 해시는 모두가 볼 수 있게 그냥 공개합니다.
해시 원본을 알아내는 것은 불가능하니까 동전을 던지기 전이라도 본인이 선택한 결과의 해시를 공개해도 문제가 없겠죠?

정말 문제가 없을까요?

A가 제출한 해시는 아래와 같습니다.

'38744961f4eda323853183a6f6bc135434347285158efaaba7b9280fdb569a95'

B가 제출한 값입니다.

'38744961f4eda323853183a6f6bc135434347285158efaaba7b9280fdb569a95'

해시를 보면 무얼 적어냈는지 모르겠네요.

동전던지기에서 가능한 입력은 head와 tail뿐이니까 해시를 한번 구해볼까요?

>>> a = b'tail'
>>> b = b'head'
>>> sha256(a).hexdigest()
'0c62f876ef1dea830de9f32c2f4b46dd6d74d50d15896e09ef5a2fcd4ac7e1d7'
>>> sha256(b).hexdigest()
'9f2e6d33a3717ee826353a404ba4618d1aeeb6879ad7936bce8ed5f46814924d'

이런, 위의 내용을 보니 금방 알아보겠네요.
A가 제출한 해시는 tail의 해시이고 B는 head의 해시를 제출했군요.
그렇습니다.

이렇게 입력 데이터가 한정된 경우(이번 예에서는 head나 tail)는 미리 입력에대한 해시를 구해보면 쉽게 제출된 해시가 어떤 값인지 분별이 가능합니다.

동전 던지기 문제의 해결 방법은?

이를 해결할 방법은 있는 걸까요?
256 bits의 랜덤한 데이터를 원본 데이터에 덪붙이면 어떨까요?
해시결과가 충돌되지 않는다는 글을 보시면 256 bits 데이터가 갖는 의미를 알 수 있을 겁니다.
이 랜덤값을 키(Key)라고 부르겠습니다.
이 키를 추가해서 해시를 구해볼까요?

>>> a = b'e6d85489f5082f21b2ecb993c799bb45e3ba7ca24618c553ff3b650b2b4e0c9btail'
>>> b = b'96f77711bac6bb95014d94f3b19c604176412101054d6d439a9b142ed2642278head'
>>> sha256(a).hexdigest()
'6c8f20b2fea69cded0afe060ebb80b79a5d0e850f1c1f621c0aadb09454f49d0'
>>> sha256(b).hexdigest()
'3401b7f3ab6d4f1555bf53dc0692611c900fb29fcb9679d5ac6b65880d7847d3'

A와 B가 선택한 값의 앞에 랜덤 키를 추가해서 해시를 구했습니다.
입력에 랜덤 키가 들어가서 해시만 보고 원본이 무엇인지 알수가 없게 되었습니다.

그럼 심판 C는 어떻게 확인을 하죠?

A와 B는 각각 랜덤하게 선택한 키를 가지고 있습니다. 그리고 실제로 자기가 선택한 값을 알고 있죠.
처음에는 해시값만 C에게 제출을 했지만 C가 동전을 던진 후 결과를 확인할 때는 키와 실제 본인이 선택한 값을 제출합니다.
C는 키와 선택한 값(head 또는 tail)을 붙인 후 해시를 실행해 보고 원래 제출한 해시값과 일치하는지 확인만 하면 됩니다.
그런데 만일 A가 동전 던진 결과를 보고 조작된 키와 선택한 값을 제출하면 어떻게 될까요?
가능은 하지만 그런 값을 찾는 것은 불가능에 가깝다고 앞의 글 해시결과가 충돌되지 않는다에서 설명을 했습니다.

이렇게 해서 해시의 두번째 특성까지 알아봤습니다.
다음 이어질 내용은 해시의 세번째 특성이면서 비트코인 마이닝과 관련된 내용입니다.

이전 글은 아래 링크에서 보실 수 있습니다.
[블록체인]해시함수 이해1 - 해시함수란?
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.
[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다(현재글)
[블록체인] 해시함수 이해 4 - 특성 3 - 퍼즐 게임을 만들 수 있다


https://steemit.com/kr/@endiyou/2-1


[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.

 in kr •  2 年前  (edited)

암호화폐 기술에 대한 내용에 항상 등장하는 것 중 하나가 해시함수(Hash Function)죠.
해시함수는 무엇일까요?

그 두번째 글로 "특성 1 : 충돌이 잘 발생하지 않는다"입니다.

충돌 저항성이라고 하는데 영어로는 Collision-registance라고 하죠. 충돌에 저항력이 있다.

이전 글은 아래 링트에서 보실 수 있습니다.
[블록체인] 해시함수 이해 1 - 해시함수란?

여기서 충돌(collision)은 해시함수의 결과 값이 동일하다는 말입니다.
hash function image (3).png

이전 해시함수란? 에서 해시 함수는

어떤 길이의 데이터를 입력해도 정해진 길이의 결과를 주는 함수

라고 간단히 이야기했습니다.

SHA256 해시는 결과가 256 bits입니다.
입력으로 사용할 수 있는 값은 무제한이고 결과는 256bits로 한정되어 있다는 말이죠.

그렇다면 2^256 + 1개의 다른 입력을 해시함수에 입력을 하면 100% 같은 결과를 갖게 됩니다.

2^130 + 1 개의 다른 입력을 시도하면 결과 중에 같은 값을 갖게될 확율이 99.8%라고 합니다.

특성 1 : 충돌이 거의 발생하지 않는다

정해진 길이의 결과를 출력하는 해시함수의 특성상 무한대의 입력을 사용할 수 있다면 충돌되는 값을 찾는 것은 100% 가능합니다.
그래서 충돌이 없다(Collision free)라고 말하지 않는 것입니다.
대신 충돌되는 경우를 찾기가 힘들다라고 말하는 이유죠.

실제로 충돌이 되는 값을 찾기위해서는 정말, 정말, 정말..... 오랜 시간이 걸립니다.
인류가 지금까지 만들었던 모든 컴퓨터를 다 동원해서 우주의 시작부터 지금까지 시도를 해도 못 찾을 거라고 합니다.
그건 2^256이라는 숫자가 얼마나 큰 숫자인지 보면 좀 더 이해가 됩니다.

2^256이라는 숫자가 얼마나 큰 숫자인가

아래 숫자를 한번 보세요.
hash function image (2).png
2^256이라는 값은 약 40억이라는 숫자를 8번 곱한 것과 같습니다.
그럼 이 숫자가 실제로 어떤 느낌인지 한번 볼까요?
2018-03-28_23-19-57.jpg
(출처 : How secure is 256 bit security?)

위 그림에 있는 숫자들이 의미하는 것을 하나씩 보겠습니다.

1. 초당 40억개의 해시를 계산하는 GPU

이런 GPU가 있다고 가정을 하는 것입니다. 이런 GPU를 장착한 컴퓨터가 있다는 가정입니다.

2. 이런 컴퓨터를 40억개 보유한 회사

실제 구글도 수백만개의 컴퓨터 정도가 있을테고 실제로 GPU 성능은 우리의 가정보다는 낮습니다.
이런 회사를 수퍼 킬로 구글이라고 부릅니다.

3. 이런 회사가 지구에 40억개

지구 전체 인구가 약 73억 정도입니다. 그중 절반이 수퍼 킬로 구글을 하나씩 소유하고 있습니다.

4. 이런 지구가 은하계에 40억개

은하계에 별의 갯수가 대략 1000억개에서 4000억개 있다고 하는데요. 그중에서 100개 중 하나가 지구와 같다는 가정입니다.

5. 이런 은하계가 40억개

이런 은하계가 온 우주에 40억개 있다는 가정입니다.
여기까지만 해도 충분히 얼마나 큰지 이해가 가네요.
그래도 끝까지 한번 가 보죠.

6. 40억 초는....

40억초는 대략 126.8년이라고 하네요.

7. 126년 X 40억은?

대략 5천70억년이라고 하는데요. 현 우주의 나이의 37배라고 합니다.

8. 여기까지 계산을 했더라도 아직 40억분의 1

맞습니다. 이제 40억분의 1을 계산한거에요...

자 이정도면 충돌이 없다라고 말해도 되는 거 아닌가요?
이제 똑같은 계산 결과를 만들어내는 입력 값을 찾는 것은 불가능하다는 것이 이해가 되시나요?=
바꿔 말하면

해시가 같다는 것은 입력값도 같다는 것이다.

라고 할 수 있죠.

그럼 이 해시로 뭘 할 수 있을까요?

데이터 요약본(Message Digest)

Alice가 중요한 데이터가 담긴 10 기가짜리 파일을 구글드라이브에 올렸습니다. 그리고 Bob에게 그 파일을 다운받으라고 알려줍니다.
그런데, Bob이 다운받은 파일은 정말 Alice가 올린 파일과 1 bit도 다르지 않은 동일한 파일일까요?
이를 확인하는 방법은 Bob이 자기가 받은 파일을 구글 드라이브 어딘가 다시 올리고 Alice가 받아서 원본과 비교해 보는 것입니다.
그런데, 만일 Bob이 아직도 전화선으로 인터넷이 연결되어 있는 곳에 살고 있다면요.
아마 다운로드 받는데는 10시간 정도 걸렸겠지만 다시 올리는데는 10 일이 걸릴지도 모르겠습니다.
이때 해시를 이용하합니다.
Alice가 자기가 업로드한 파일의 SHA256 해시를 계산해서 Bob에게 줍니다.
Bob은 다운로드 받은 파일의 SHA256 해시를 구해서 Alice가 준 것과 비교합니다.
10기가의 파일을 다시 올리는 대신에 요약된 256 bits로 비교가 가능해 집니다.
해시가 같다는 것은 입력된 값도 같다는 것이니까요.

데이터 무결성 증명

Alice가 업로드한 파일은 실제로 아주 중요한 데이터를 가지고 있습니다. 단 한글자라도 변경이 되면 안되는 중요한 데이터죠.
Bob이 나쁜 마음을 품고 데이터를 살짝 변경해서 회사의 경영자인 James에게 전달할 수도 있죠.
Bob을 못 믿는 James는 자기가 받은 파일이 Alice가 준 것과 동일한 지 확인해야합니다.
방법은 Alice한테 연락을 해서 파일을 다시 올리면 직접 다운받아서 Bob이 준 것과 비교하는 것이죠.
그런데 그럴 거면 뭐하러 Bob한테 받겠어요.
이때도 해시를 이용하면 됩니다.
Alice한테 SHA256해시만 미리 받아두고 있다가 Bob이 준 파일의 해시를 계산해서 비교해 보는 거죠.
역시 해시가 같다는 것은 입력된 값도 같다는 것이니까요.

여기까지 해시의 특성 첫번째까지 알아봤습니다.
내용을 보시면서 블록체인에서 어떻게 이용될지 좀 감이 오시나요?
나머지 두가지 특성에 대해서도 이어지는 포스트에서 설명하겠습니다.

관련 포스트 목록입니다.
[블록체인] - [블록체인] 해시함수 이해 1 - 해시함수란?
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.(현재글)
[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다
[블록체인] 해시함수 이해 4 - 특성 3 - 퍼즐 게임을 만들 수 있다


https://steemit.com/kr/@endiyou/1


[블록체인] 해시함수 이해 1 - 해시함수란?

 in kr •  2 年前  (edited)

블록체인, 암호화폐 기술에 대한 내용에 항상 등장하는 것 중 하나가 해시함수(Hash Function)죠.
해시함수는 무엇일까요?
정말 쉽고 간략하게 설명해 보겠습니다.

해시함수를 아주 간단히 말하면,

어떤 길이의 데이터를 입력해도 정해진 길이의 결과를 주는 함수라고 할 수 있습니다.

말은 정말, 정말 간단하죠? 단지 확~~ 마음에 와 닫지 않을 뿐.

아래 그림은 SHA256 해시 함수를 그림으로 간략히 표시한 겁니다.
hash function image.png

말로는 잘 이해가 안될 수도 있으니 실제 SHA256 해시의 결과를 확인해 보겠습니다.
SHA 256은 256bits의 결과를 주는 해시함수인데요. 파이썬 hashlib에서 제공하는 함수를 사용했습니다.

t1 = b'Hello, SHA 256'
sha256(t1).hexdigest()

결과는

'a110c0e99329666d8ed6aa919a6875e79106b6b8c3e906a6145b44d8240d988c'

입니다.

결과가 16진수로 표시되었기때문에 각 글자당 4bits이라서 4 X 64 글자 = 256 bits 입니다.

t2 = b'This is a sha 256 test. The input length can be random'
sha256(t2).hexdigest()
'37ea99e0274c2d1f72c35dc291578717b43d31a76b17e260977a6c6ac9fb5755'

다른 길이의 입력을 넣어도 결과는 항상 256bits입니다. SHA256 이니까요.

해시함수는 아래와 같은 특성을 가지고 있습니다.

  • 어떤 길이의 데이터도 입력으로 사용될 수 있습니다.
  • 결과는 정해진 길이로 나옵니다. SHA256은 입력되는 데이터의 길이와 상관없이 항상 256bits를 결과로 줍니다.
  • 계산 시간이 합리적으로 추정 가능해야합니다. 입력 길이에 제한이 없기때문에 최소한 입력 길이에 선형적으로 비례하는 특성은 있어야합니다.

정말 간단하죠?

입력 길이에 상관없이 정해진 길이의 결과를 내어주는 함수 == 해시함수

하지만 정말 재미있는 해시함수의 특징이 있습니다.
특별히 블록체인에서 관심있어하고 실제로 사용되는 특성은 아래와 같은데요.

  • 결과값이 중복될 가능성이 거의 없다.
  • 입력값을 알수 없다.
  • 결과값을 알려주고 입력값을 찾을 수 있는 특별한 공식이 없다.

위의 특성을 하나하나 보다보면 왜 블록체인에서 해시함수를 자주 언급하는지 알게됩니다.

다음 포스트에는 위 3가지 특성에 대해서 좀 더 자세히 알아보도록 하겠습니다.

관련 포스트 목록입니다.
[블록체인] 해시함수 이해 1 - 해시함수란?(현재글)
[블록체인] 해시함수 이해 2 - 특성 1 - 충돌이 거의 발생하지 않는다.
[블록체인] 해시함수 이해 3 - 특성 2 - 원본 내용을 알 수 없다
[블록체인] 해시함수 이해 4 - 특성 3 - 퍼즐 게임을 만들 수 있다


+ Recent posts