-- 1 Building Abstractions with Procedures
-- 프로세스 process = 데이터를 조작하면서 수행하는 일련의 행동. 마법.
-- 프로그램 program = 프로세스가 작동하는 방법을 기술한 규칙. 마법서.
-- 프로그래밍 언어 programming language = 마법서를 적는 데 필요한 말.
-- 프로그래머 programmer = 마법서를 적는 사람. 마법사.
-- 프로그래머의 소양 =
-- 깊은 지식, 경험, 조심성.
-- 프로그램의 구조를 제대로 짤 줄 안다.
-- 오류를 찾아서 제대로 고칠 줄 안다.
-- 좋은 프로그램의 조건 = 부품 단위로 만들고, 고치고, 갈아 끼울 수 있다.
-- Lisp로 프로그램을 짠다는 것 = Lisp이 할 수 있는 것은 Haskell도 할 수 있다.
-- Haskell =
-- 함수형 언어의 표준을 목표로 하여 만들어진 언어.
-- 순수 함수형, 지연 평가, 타입 추론.
-- 오랜 기간동안 최적화된 컴파일러 GHC, 간단하고 유용한 REPL GHCi.
-- 환경, 빌드, 패키지 관리 등 통합 도구 stack.
-- 1.1 프로그래밍의 기본 요소 The Elements of Programming
-- 좋은 프로그래밍 언어의 세 가지 표현 방식 =
-- 기본 식 primitive expression = 언어에서 가장 간단한 것.
-- 엮어내는 수단 means of combination = 식을 모아 복잡한 것으로 만든다.
-- 요약하는 수단 means of abstraction = 복잡한 것에 이름붙여 간단히 쓴다.
-- 프로그래밍의 두 가지 요소 = 프로시저, 데이터.
-- 데이터 사용하는 것. 자료.
-- 프로시저 procedure = 데이터를 처리하는 규칙. 함수.
-- 좋은 프로그래밍 언어에는 기본 데이터, 기본 프로시저, 요약하는 수단이 있다.
-- 1.1.1 식 expression
-- 스킴에 실행기가 있는 것처럼, 하스켈에도 실행기가 있다.
-- 실행기는 식을 넣으면, 식을 계산하여, 결과를 보여준다. 이를 반복한다.
-- 이를 REPL이라고 한다. Read-Eval-Print Loop.
-- 486을 입력하면 486이 출력된다.
-- (+) 137 349
-- 486
-- 이건 그냥 이렇게 써도 된다.
-- 137 + 349
-- 486
-- 하스켈에서 기호로만 이루어진 함수는 기본적으로 중위 연산자이다.
-- 중위 연산자를 전위로 쓰고 싶으면 괄호로 감싼다.
-- (*) 5 99
-- 495
-- 식의 구조 = (f a1 a2 ...)
-- 첫째는 함수(연산자 operator)가 온다.
-- 둘째 이후에는 인자(피연산자 operand)가 온다.
-- (+ 21 35 12 7) 은 하스켈에서 이렇게 쓴다.
-- 27 + 35 + 12 + 7
-- 75
-- 아니면 이렇게...
-- foldr1 (+) [21, 35, 12, 7]
-- 75
-- 하스켈에서 (+)는 인자 두 개만 받을 수 있는 binary function이기 때문이다.
-- 복잡한 식도 문제 없다.
expr1 = (+) ((*) 3 ((+) ((*) 2 4) ((+) 3 5))) ((+) ((-) 10 7) 6)
-- 1.1.2 이름과 환경 naming and environment
-- 스킴에서는 define을 쓰지만, 하스켈에서는 =를 쓴다.
-- (define size 2)는 이렇게 쓴다.
size = 2
circumference r = 2 * pi * r
-- 정의 =는 식을 요약하는 수단이다.
-- 환경 environment = 정의한 (이름, 식) 쌍은 실행기 메모리상에 저장된다.
-- 1.1.3 엮은식을 계산하는 방법 evaluating combinations
-- 식의 구조 = (f a1 a2 ...)를 기억하는가?
-- 식이 여러 개로 엮일 수 있는 것도 알고 있다.
-- 엮은식의 내부는 여러 개의 부분 식으로 이루어져 있다.
-- 실행기는 엮은식을 여러 개의 부분 식으로 해석한다.
-- 각각의 부분 식들 역시 더 작은 부분 식으로 분해하여 해석한다.
-- 더 이상 분해할 수 없을 때 까지 분해한다. 분해한 식의 구성은 트리 모양이다.
-- 분해할 수 없는 기본 식 =
-- 수식 = 리터럴.
-- 붙박이 연산자 = built-in operator. 언어가 미리 정의해 둔 계산.
-- 이름 = 다른 환경에서 정의한 식.
-- x = 3과 같은 =를 통한 식의 정의는 언어가 특별히 정의한 문법이다.
-- x = 3을 일반적인 식처럼 해석하면 답이 안 나온다.
-- x를 함수, =와 3을 인자라고 생각하면 계산이 될까?
-- 이러한 것을 특별형태 special form 라고 한다.
-- 1.1.4 묶음 프로시저 compound procedure
-- 좋은 프로그래밍 언어의 요건을 다시 생각해보자 =
-- 수와 산술 연산 등 기본 데이터와 기본 프로시저가 제공된다.
-- 식을 엮어 쓸 수 있다.
-- 이름을 붙여 정의할 수 있다.
-- (define (square x) (* x x))
square x = x * x
-- 이를 묶음 프로시저 compound procedure 라고 한다.
-- 프로시저를 정의하는 방법 =
-- <name> <formal parameters> = <body>
-- <name> = 프로시저의 이름. 환경에 저장된다.
-- <formal parameters> = 프로시저가 받는 인자 이름. <body>에서 사용된다.
-- <body> = 프로시저가 계산할 식.
-- 이름붙은 프로시저는 다음과 같이 다른 식에서 끌어다 쓸 수 있다.
sumofsquares x y = square x + square y
expr2 a = sumofsquares (a + 1) (a * 2)
-- 1.1.5 치환으로 프로시저를 실행하는 방법
-- 프로시저가 적용되는 규칙 =
-- 프로시저 body 속의 모든 인자를 모두 전달된 값으로 치환한다.
-- 이렇게 얻은 식의 값을 계산한다.
-- 식의 값을 구하는 방법 =
-- 인자 먼저 계산법 applicative order = strict evaluation.
-- 식을 적용하는 순서대로 바로 계산하는 방식이다.
-- 정의대로 계산법 normal order = lazy evaluation.
-- 식을 가능한 분해해서 필요할 때 까지 미루다가 계산하는 방식이다.
-- 스킴은 strict지만, 하스켈은 lazy이다.
-- 1.1.6 조건 식과 술어 conditional expressions and predicates
-- 조건에 따른 분기별로 값을 정할 수 있다.
abs x
| x > 0 = x
| x == 0 = 0
| x < 0 = -x
abs' x = if' (x < 0) (-x) x
-- x > 0 과 같이 참, 거짓을 판별할 수 있는 식을 술어 predicate 라고 한다.
-- if는 p가 하나일 때 유용하다.
-- if <predicate> <consequent> <alternative>
-- 특별형태 if는 다음과 같이 함수로 구현할 수 있다.
if' :: Bool -> a -> a -> a
if' True c _ = c
if' False _ a = a
--
-- 술어는 ==, > 이외에도 and, &&, or, || 등을 쓸 수 있다.
isin5to10 x = x > 5 && x < 10
-- 하스켈 표준 라이브러리에 >=, <이 정의되어 있으므로 약간 다르게 쓰자.
x .>=. y = x > y || x == y
x .<. y = not $ x .>=. y
-- 연습문제 1.2
expr3 = (/)((+)((+)5 4)((-)2((-)3((+)6((/)4 5)))))((*)((*)3((-)6 2))((-)2 7))
expr3' = (5+4+(2-(3-(6+4/5))))/(3*(6-2)*(2-7))
-- 연습문제 1.3
-- 세 숫자를 인자로 받아 그 중 큰 숫자 두 개를 제곱한 다음, 그 둘을 더한다.
sumoflargersquares a b c
| c < a && c < b = sos a b
| b < a && b < c = sos a c
| otherwise = sos b c
where sos = sumofsquares
-- 1.1.7 연습 : 뉴튼 법으로 제곱근 찾기
-- 함수와 프로시저의 차이 =
-- 함수 = 무엇이 어떤 성질을 지니는지 밝히는 일. 선언.
-- 프로시저 = 그 무엇을 어떻게 만들지 또는 구할지 나타내는 일. 명령.
-- 뉴튼 법 = 제곱근 값을 근사적으로 구하는 방법.
-- x의 제곱근을 구하려고 한다. 제곱근 값에 가까운 값 y가 주어진다.
-- y와 x / y의 평균을 구한다. 이 값은 새로운 y가 된다.
-- 이 작업을 반복한다.
-- x와 y의 제곱을 비교해본다.
sqrtiter guess x = if' (isgood guess x) guess (sqrtiter (improve guess x) x)
improve guess x = average guess (x / guess)
average x y = (x + y) / 2
isgood guess x = (abs' ((square guess) - x)) < 0.001
sqrt' x = sqrtiter 1.0 x
-- 연습문제 1.6
-- if를 condition 구문으로 재정의하기.
-- 이것을 뉴튼 법 코드에 적용해서 잘 작동하는지 시험해보자.
newif p c a | p = c | otherwise = a
sqrtiter' guess x = newif (isgood guess x)
guess
(sqrtiter' (improve guess x) x)
-- 잘 작동한다!
-- 연습문제 1.8
-- 세제곱근을 구하는 뉴튼 법 프로시저를 짜보자.
cubeiter guess x = if' (isgood guess x) guess (cubeiter (improve guess x) x)
where improve guess x = (x / guess ** 2 + 2 * guess) / 3
isgood guess x = (abs' ((guess ** 3) - x)) < 0.001
cbrt x = cubeiter 1.0 x
-- 1.1.8 블랙박스처럼 간추린 프로시저
-- 재귀 recursive 프로시저 = 프로시저 정의에서 자기 자신을 불러 쓰는 경우.
-- 프로시저를 쓰면서 큰 문제가 작은 문제로 자연스럽게 나뉜다.
-- 추측값이 쓸만한지 따지는 것 = isgood 함수.
-- 더 좋은 값을 구하는 것 = improve 함수.
-- 프로시저를 나누는 중요한 기준 = 작업 단위.
-- 프로시저를 끌어다 쓸 때, 해당 프로시저의 자세한 계산을 알 필요는 없다.
-- 갇힌 이름 local name
-- 매개변수 이름은 프로시저가 뜻하는 바에 아무런 영향을 주지 않아야 한다.
-- 프로시저에 갇혀야 local 한다.
-- 프로시저 본문 body 에서만 쓰는 이름이어야 한다.
-- 종속 변수 bound variable = 프로시저 내부에 매인 bind 변수.
-- 자유 변수 free variable = 프로시저 정의에 매이지 않은 변수.
-- 종속 변수와 자유 변수 구분법 =
-- 람다 함수 f = \x y -> if' (x > a) x y 를 예로 들면,
-- x, y는 종속 변수이다.
-- 종속 변수는 포인트 point 라고 하며, reduce 할 수 있다.
-- f = (> a) >== if' 과 같이 다시 쓸 수 있다.
-- a는 자유 변수이다.
-- 자유 변수를 갖는 함수를 클로저 closure 라고 한다.
-- 안쪽 정의 internal definition 와 블록 구조 block structure
-- 이름 충돌을 막고 구현을 외부로부터 은닉 capsulation 하기 위해.
sqrt'' x = sqrtiter 1.0 x
where
isgood guess x = (abs' ((square guess) - x)) < 0.001
improve guess x = average guess (x / guess)
sqrtiter guess x = if' (isgood guess x)
guess
(sqrtiter (improve guess x) x)
-- 프로시저 내부 정의에서 x를 자유 변수로 만들어도 된다.
-- 프로시저는 이미 x를 알고 있기 때문에 따로 인자로 전달할 필요가 없다.
sqrt''' x = sqrtiter 1.0
where
isgood guess = (abs' ((square guess) - x)) < 0.001
improve guess = average guess (x / guess)
sqrtiter guess = if' (isgood guess)
guess
(sqrtiter (improve guess))
-- 1.1 끝
글 써지나?
아까 쓸라고 했는데 안 써지길래 github 계정 하나 만들어서 올린다.
깃헙은 여기 -> https://github.com/funcslam/hasicp/blob/master/ch1_procedure/1.1.hs
내가 직접 공부하면서 쓴 거고, 하스켈로 했어.
λάμδαhu Akbar!
히익 하스켈
일단 추천
츄럴이 SICP글 올린거 보고 나도 가다듬어서 올리는거. ^_^
전 책 요약정리는 안 할거라서 계속 올려주세용