자 이제부터 2강에서는 진위값에 대해 알아보는 시간을 가져보겠습니다.
1.동등 비교와 기타 비교들
앞 장에서는 다음 코드처럼 등호를 사용하여 변수와 함수를 정의하였습니다.
r = 5
이 코드를 프로그래밍적으로 평가하자면 (스코프 안에서) r을 모두 5로 치환한다는 뜻이 됩니다.
f x = x + 3
다음 코드에서는 f다음 숫자에 (f의 인자) 가 붙는 모든 자리에 숫자 3을 더한 것으로 치환됩니다.
그렇다면 수학에서 등호는 중요한 요소가 되지만 미묘하게 다른 방식으로도 쓰이게 되는데 그 예로
x + 3 = 5
이 문제에서 우리의 괸심사는 값 5를 x + 3 으로 표현하거나 x + 3을 5로 표현한다는 것이 아닌 등식 x + 3 = 5를
명제(proposition)로서 보고, 어떤 숫자에 x를 3으로 더하면 5가 나온다고 읽게 됩니다.
이 등식을 푼다는 것은 이 명제를 참으로 만드는 x 값을(만약 있다면) 찾아낸다는 뜻이 됩니다.
예로써 초등 대수를 이용해서 x = 2라고 판단내릴 수 있게 됩니다(즉 x 값이 2라는 것은 이 등식을 참으로 만드는
숫자이며 2 + 3 = 5 가 된다.)
값들이 동일한지 비교하는 일은 프로그래밍에서도 유용하게 사용할 수 있습니다.
하스켈에서 그런 검사는 단순히 등식처럼 보일 수 있습니다.
등호는 이미 정의를 위해 쓰고 있기 때문에 하스켈에서는 그 대신 (이중등호 ==)를 사용합니다.
위 명제를 GHCi를 사용해 봅시다.
Prelude) 2 + 3 = 5
True
GHCi는 "True"로 반환하는데, 2+3은 5와 같기 때문입니다. 참이 아닌 등식을 사용하면 어떨까요?
3 + 7 == 11
False
값이 틀리기 때문에 그대로 False를 반환합니다. 다음은 함수를 직접 만들어 활용해보면
Prelude> let a c = c + 5
Prelude> a 4 == 9
True
a 4 는 5 + 9로 평가되기 때문에 True가 반환됩니다.
두 숫자값 중 무엇이 큰지도 비교할 수 있습니다. 하스켈에서는 < (미만) , > (초과), <= (이하), >= (이상) 등
많은 검사를 제공합니다. 이 검사들을 == (동일)과 같은 방식으로도 작동합니다.
예를들어 < (미만)을 이전에 배운 area 함수를 사용해 특정 반지름의 원이 어떠한 값보다 작은지
확인이 가능합니다.
Prelude> let area r = pi * r ^ 5
Prelude> area 8 < 80
False
2.불리언 값
GHCi가 이러한 산술 명제를 참인지 거짓인지를 결정할 때 실제로 어떠한 일이 발생할까요?
조금 다른 이야기지만 연관 있는 다음 문제를 생각해봅니다. GHCi에 산술 표현식을 이용하면
그 표현식은 평가가 되고, 결과 산술값이 화면에 표시됩니다.
Prelude> 3 + 4
7
산술 표현식을 이용해 항등 비교를 대체하면 비슷한 결과가 됩니다.
Prelude> 5 == 5
True
"5"는 일종의 셈, 수량 같은 것을 나타내고, "True"는 명제의 참을 의마하는 값(value)입니다.
이러한 값을 진위값(true value) 또는 불리언 값(boolean value)라고 부릅니다.
자연스럽게 불리언 값은 오직 True & False 만이 존재합니다.
3.타입 입문
True 와 False는 단순히 비유가 아닌 실제로 존재하는 값입니다. 불리언 값은 하스켈에서 숫자값과
같은 신분을 가지며 비슷한 방식으로 조작할 수 있습니다. 다음과 같은 예시로
Prelude> True == True
True
Prelude> True == False
False
True(참)은 True와 같고 True는 False와 같지 않습니다.
그렇다면 2는 True와 같을까요?
Prelude> 2 == True
<interactive>:23:1: error:
• No instance for (Num Bool) arising from the literal ‘2’
• In the first argument of ‘(==)’, namely ‘2’
In the expression: 2 == True
In an equation for ‘it’: it = 2 == True
이 공식자체는 성립이 되지 않습니다. 숫자를 숫자가 아닌 것과 비교하거나 불리언 값을 불리언이 아닌 값과
비교하는 것 자체가 불가능하기 때문입니다. 하스켈에서는 이 개념을 받아들이며,
위처럼 오류 메시지를 뱉어냅니다. 간단히 말하면 이 메시지는 ==의 좌변에 숫자(num)이 있고
우변에 숫자를 원한다고 말하고 있습니다. 하지만 불리언 값(bool)은 숫자가 아니게 되므로
항등 검사에 실패하게 됩니다.
즉 값이 타입(type)을 가지며, 타입은 우리가 값을 가지고 무엇을 할 수 있고 무엇이 불가능한지 그 한계를
정의하게 됩니다. True와 False는 각각 Bool 타입의 값입니다.
2라는 숫자를 말하자면 조금 복잡한데 숫자 타입이 여러게 있어 설명하기가 복잡하기에 다음에 설명하겠습니다.
타입(Type)은 매우 강력한 도구로써 값들이 말이 된다는 규칙 하에 행동하도록 제한하고
올바르게 작동하는 프로그램의 작성을 매우 쉽게 만들도록 기능을 제공하고 있습니다.
타입은 하스켈에서 아주 중요한 포인트이기에 앞으로 자주 논하게 될 것 입니다.
4.중위 연산자 (infix operator)
2 == 2와 같은 항등 검사는 2 + 2 처럼 단지 표현식이며 거의 같은 방식으로 값으로 평가됩니다.
표현식이라는 것은 앞선 예제의 오류 메시지에서도 말하고 있습니다.
In the expression: 2 == True
우리가 프롬프트에 2 == 2를 입력하고 GHCi가 True로 "대답"할 때, GHCi는 단지 표현식을 평가하고
있는 것입니다. ==는 그 자체가 인자 두 개를 받는 함수며 (두 인자가 각각 항등 검사의 좌변과 우변에 위치)
그 문법은 다소 인상적입니다. 하스켈은 2-인자 함수를, 인자들 사이에 위치하는 중위 연산자로 사용하는 것을
허용합니다. 함수 이름에 알파벳이나 숫자가 들어가지 않을 경우에는 이런 중위 표기는 흔한 용법입니다.
이러한 함수를 "표준" 방식으로(함수 이름이 인자들보다 앞에 오는, 전위 연산자로서)
사용하려면 함수 이름을 괄호로 감싸야 합니다. 즉 다음 표현식들은 서로 동등합니다.
Prelude> 4 + 9 == 13
True
Prelude> (==) (4 + 9) 13
True
여기서 (==)가 앞에서 본 areaRect와 비슷하게 작동하는 것을 볼 수 있습니다. 이러한 개념은
다른 관계 연산자들(<, >, <= , >=)과 산술 연산자들(+, * 등)에도 적용 됩니다.
이것들은 모두 인자를 두 개 취하는 함수이며 보통은 중위 연산자로 작성됩니다.
일반화하면 하스켈에서 실체가 있는 것들은 값 또는 함수라고 말할 수 있습니다.
5.불리언 연산
하스켈은 진위값을 논리 명제로서 조작하기 위해 기본 함수를 3개 제공합니다.
- (&&)은 and 연산자(논리곱)을 수행하며 두 불리언 값이 주어질 때 둘다 True인 경우 True로
그 외의 경우 False로 평가됩니다.
Prelude> (4 < 5) && (False == False)
True
Prelude> (&&) (8 <= 7) (1 == 1)
False
- (||)은 or연산자(논리합)을 수행합니다. 두 불리언 값이 주어질 때 둘 중 하나라도 True일 경우 True
그 외의 경우 False로 평가됩니다.
Prelude> (2 + 2 == 5) || (2 > 0)
True
Prelude> (||) (25 == 20) (5 >= 12)
False
- not은 불리언 값의 반전(negation)을 수행합니다, 즉 True는 False로 , False는 True로 반환합니다.
Prelude> not (5 * 2 == 10)
False
Prelude> not (5 * 2 == 11)
True
하스켈 라이브러리에서는 같지 않음(not equal to)를 위한 관계 연산자 함수 (/=)가 들어 있지만,
우리가 쉽게 직접 구현할 수 있습니다.
x /= y = not (x == y)
중위 표기는 연산자를 정의할 때도 가능합니다. ASCII 기호를(키보드에서 가장 흔히 쓰는 기호들)을 사용해
완전 새로운 연산자를 만들 수 있습니다.
6.가드(gaurd)
하스켈 프로그램은 불리언 연산자를 종종 간편하고 축야된 구문으로 사용합니다. 동일한 로직을 다른 식으로
작성하는 것을 편의 문법(syntactic sugar)이라고 부르는데, 사람이 보기에 코드가 더 이뻐보이기 때문입니다.
그런 문법 중 가드(gaurd)문법을 알아봅시다. 가드는 불리언 값에 기반해 단순하지만 강력한
함수들을 작성할 수 있습니다.
절대값 함수를 구현해보면, 실수의 절대값은 그 수에서 기호를 제거한 것으로, 음수면(0보다 작으면) 그 기호를
반전시키고 음수가 아니면 그대로 둡니다. 그 정의를 다음과 같이 적을 수 있는데
여기서 |x|를 계산하기 위해 쓰는 실제 표현식은 x에 관해 새운 명제들의 집합에 따라 달라집니다.
만약 x ≥ 0가 참이면 첫 번째 표현식을 사용하고
x < 0이면 두 번째 표현식을 대신 사용합니다. 가드를 사용할 시 다음과 같이 구현할 수 있습니다.
예제: 절대값 함수
absolute x
| x < 0 = -x
| otherwise = x
위의 코드는 이에 대응하는 수학적 정의만큼이나 가독성을 가지고 있습니다. 위 정의를 하나씩 설명해보면
- 일반적인 함수 정의처럼 시작한다. 함수 이름 absolute를 적고 이 함수가 단일 매개변수 x를 취한다고 알린다.
- ( = )와 정의의 우변을 적는 대신 줄 바꿈을 하고, 두 대체문을 별개의 줄에 놓았다. 이 대체문들을 가드라고 한다.
공백부분은 단순 이유로 넣은 것이 아닌 코드가 올바르게 파싱되기 위해 필수 요소이다. - 각각의 가드는 파이프 문자인 ( | )로 시작하며, 파이프 뒤에 불리언 값으로 평가될
표현식(불리언 조건식 또는 술어식(predicate)라고도 부름)을 놓고, 그 뒤에는 정의의 나머지 부분이 온다.
함수는 술어식이 True로 평가되는 줄의 등호와 우변만을 사용한다. - otherwise 분기는 선행하는 술어식 중 True로 평가되는 것이 없을 때 사용된다. 이 경우에는
x가 0보다 작지 않다면 x는 0과 같거나 그보다 커야한다. 따라서 마지막 술어식은 x >= 0으로 쓸 수 있으나
otherwise로도 잘 작동한다.
여기서 잠깐 설명하자면
otherwise의 이면에 문법상에 마법은 존재하지 않습니다. otherwise는 하스켈의 다른 기본함수, 함수들과
함께 정의되어 있으며 단순히
otherwise = True
이다. 이 정의는 otherwise를 전방 수비 가드로 만들며, 가드 술어식의 평가는 순차적이므로
다른 술어식들 중 하나도 True로 평가되지 않을 때만 otherwise에 도달합니다.
그렇기 떄문에 otherwise를 마지막 가드로 세웠는지를 확인해보고.
일반적으로는 항상 otherwise를 두는것이 좋습니다. 어떠 입력에 대해 아무 술어식도
참이 아닐경우 그 즉시 런타임 오류가 발생하기 때문!!
첫번째 가드에서
| x < 0 = -x
x를 반전하기 위해 마이너스 기호를 사용했습니다. 기호 반전을 이렇게 표현하는 것은 일종의 특수 사례로
( - )는 인자를 하나 받아서 0 - x로 평가하는 함수가 아닌 축약 문법일 뿐입니다.
이러한 축약은 편리해도 가끔 ( - )를 실제로 함수로써 사용하는 것(뺄셈 연산자)과 충돌해
성가심의 원인이 될 수도 있으니 (괄호로 묶지 않고 음수 네 개를 가지고 뺄셈을 세 번 해본다)
음수를 가지고 absolute를 테스트해보려면 아래와 같이 호출해야 합니다.
Prelude> absolute (-10)
10
7, where과 가드
where절은 특히 가드와 함께 사용하면 편리합니다
위 식의 (실수인) 해의 개수를 구하는 함수를 살펴봅시다.
numOfSolutions a b c
| disc > 0 = 2
| disc == 0 = 1
| otherwise = 0
where
disc = b^2 - 4*a*c
where의 정의는 모든 가드의 스코프 내에 있으므로 disc의 표현식을 여러번 작성하지 않아도 됩니다.
강의 자료
'백엔드 > Haskell' 카테고리의 다른 글
[Haskell] 하스켈 기초반 3강 - 타입의 기초 (0) | 2023.01.12 |
---|---|
[Haskell] 하스켈 기초반 1강 - 변수와 함수 (2) | 2022.04.11 |
[Haskell] 하스켈 이란? 함수형 프로그래밍 (0) | 2022.04.05 |