하스켈이 무엇인지(순수 함수형, 지연 평가, 정적 타입과 타입 추론), 하스켈을 배울 때의 마음가짐과 GHC/GHCi로 시작하는 방법을 소개합니다.
_위대한 선을 위한 하스켈 배우기_에 오신 걸 환영해요! 이 글을 읽고 있다면, 아마 하스켈을 배우고 싶어서 오셨겠죠. 잘 찾아오셨어요. 다만 그 전에 이 튜토리얼에 대해 잠깐 이야기해 볼게요.
제가 이 글을 쓰기로 한 이유는 제 하스켈 지식을 굳히고 싶었고, 또 제 관점에서 하스켈을 처음 접하는 분들에게 도움이 될 수 있겠다 싶었기 때문이에요. 인터넷에는 하스켈 튜토리얼이 꽤 많아요. 저도 처음에 하스켈을 배울 때 하나의 자료만으로 배우지 않았어요. 여러 튜토리얼과 글을 같이 읽었는데, 각 자료가 서로 다른 방식으로 설명하더라고요. 여러 자료를 훑어보니 퍼즐 조각이 맞춰지듯 딱 이해가 됐죠. 그래서 이 글도 여러분이 마음에 들어 하는 자료를 찾을 확률을 조금이라도 높여 보려는, 또 하나의 유용한 자료가 되려는 시도예요.
이 튜토리얼은 명령형 프로그래밍 언어(C, C++, Java, Python …) 경험은 있지만 함수형 언어(Haskell, ML, OCaml …)는 처음인 분들을 대상으로 해요. 물론 프로그래밍 경험이 많지 않아도, 여러분처럼 똑똑한 분이라면 충분히 따라오며 하스켈을 배울 수 있을 거예요.
Libera.Chat 네트워크의 #haskell 채널은 막히는 부분이 있을 때 질문하기에 아주 좋은 곳이에요. 그곳 사람들은 초심자에게도 정말 친절하고 인내심이 많고 이해심이 넓어요.
저는 하스켈을 이해하기까지 두 번쯤은 실패했어요. 너무 낯설게 느껴져서 손에 안 잡히더라고요. 그런데 어느 순간 ‘탁’ 하고 감이 오고, 그 초기 허들만 넘기고 나니 이후로는 순항이었어요. 결국 하고 싶은 말은 이거예요: 하스켈은 훌륭하고, 프로그래밍에 관심이 있다면 처음엔 좀 이상해 보여도 꼭 배워 보세요. 하스켈을 배우는 건 처음으로 프로그래밍을 배울 때와 아주 비슷해요 — 재미있거든요! 다르게 생각하도록 만들어 주죠. 그럼 다음 섹션으로 넘어가 볼까요 …
하스켈은 _순수 함수형 프로그래밍 언어_예요. 명령형 언어에서는 컴퓨터에 할 일의 순서를 지시하고, 컴퓨터는 그걸 차례로 수행해요. 수행하는 동안 상태가 바뀔 수 있죠. 예를 들어 변수 a를 5로 설정했다가, 뭔가를 한 뒤 다시 다른 값으로 바꾸곤 해요. 어떤 동작을 여러 번 수행하기 위한 제어 흐름 구조도 있죠. 반면 순수 함수형 프로그래밍에서는 컴퓨터에게 무엇을 어떻게 하라고 지시하기보다, 대상이 무엇인지(정의)를 말해 줘요. 예를 들어 어떤 수의 팩토리얼은 1부터 그 수까지의 곱이고, 숫자 리스트의 합은 첫 번째 숫자에 나머지 숫자들의 합을 더한 것이죠. 이런 걸 함수의 형태로 표현해요. 또 변수를 어떤 값으로 설정해 놓고 나중에 다른 값으로 바꿀 수 없어요. a가 5라고 말했으면, 나중에 그게 다른 값이라고 말할 수 없죠. 뭐죠, 거짓말쟁이라도 되려고요? 그래서 순수 함수형 언어에서 함수는 부수효과가 없어요. 함수가 할 수 있는 일은 어떤 값을 계산해서 결과로 돌려주는 것뿐이에요. 처음엔 다소 제한적으로 보일 수 있지만, 이는 아주 멋진 결과를 가져와요: 같은 인자로 함수를 두 번 호출하면 항상 같은 결과가 보장돼요. 이를 _참조 투명성_이라고 하고, 이는 컴파일러가 프로그램의 동작을 추론할 수 있게 해 줄 뿐 아니라, 여러분이 함수가 올바름을 쉽게 유추(심지어 증명)하고, 간단한 함수들을 이어 붙여 더 복잡한 함수를 구축할 수 있게 해 줘요.
하스켈은 _게으르죠(지연 평가)_를 해요. 즉 특별히 지시하지 않는 한, 진짜로 결과를 보여 줘야 할 때까지 함수 실행과 계산을 미루어요. 이는 참조 투명성과 잘 맞아떨어지며, 프로그램을 데이터에 대한 _변환들의 연속_으로 생각할 수 있게 해 줘요. 무한한 자료구조 같은 멋진 것들도 가능하게 하죠. 예를 들어 xs = [1,2,3,4,5,6,7,8] 같은 불변 숫자 리스트와, 각 원소를 2배로 만들어 새 리스트를 돌려주는 doubleMe라는 함수가 있다고 해 봐요. 명령형 언어에서 리스트의 값을 8배로 만들려고 doubleMe(doubleMe(doubleMe(xs)))처럼 했다면, 아마 리스트를 한 번 훑으며 복사해서 반환하고, 또 두 번 더 훑은 뒤에야 결과를 돌려줄 거예요. 하지만 게으른 언어에서는, 결과를 강제로 요구하지 않은 채 리스트에 doubleMe를 적용하면 프로그램은 마치 “알았어, 나중에 할게!”라고 말하는 셈이 돼요. 그런데 여러분이 결과를 보고 싶어지는 순간, 첫 번째 doubleMe가 두 번째에게 ‘지금 결과가 필요해!’라고 말하죠. 두 번째는 세 번째에게 그렇게 요구하고, 세 번째는 마지못해 1을 두 배로 만든 2를 건네줘요. 두 번째는 그걸 받아 4를 첫 번째에게 주고, 첫 번째는 그걸 보고 여러분에게 첫 번째 원소가 8이라고 알려줘요. 결국 정말 필요할 때 리스트를 단 한 번만 훑게 되죠. 이렇게 게으른 언어에서는 초기 데이터만 잡고서, 필요할 때마다 효율적으로 변환하고 이어 붙이며 최종적으로 원하는 모양을 만들어 낼 수 있어요.
하스켈은 정적 타입 언어예요. 프로그램을 컴파일할 때, 컴파일러는 어떤 코드 조각이 숫자인지, 문자열인지 등을 알고 있어요. 즉 많은 잠재적 오류가 컴파일 시점에 잡힌다는 뜻이죠. 숫자와 문자열을 더하려고 하면 컴파일러가 불평(오류)을 쏟아낼 거예요. 하스켈은 _타입 추론_을 갖춘 아주 훌륭한 타입 시스템을 사용해요. 즉 모든 코드에 타입을 일일이 달 필요가 없고, 타입 시스템이 많은 걸 똑똑하게 추론해 줘요. a = 5 + 4라고만 써도 a가 숫자라는 걸 하스켈이 스스로 알아내죠. 타입 추론 덕분에 코드가 더 일반적으로 쓸 수 있게 되기도 해요. 예를 들어 여러분이 두 인자를 받아 더하는 함수를 만들었는데 타입을 명시하지 않았다면, 그 함수는 ‘숫자처럼 동작하는’ 어떤 두 인자에도 똑같이 동작해요.
하스켈은 _우아하고 간결_해요. 고수준 개념을 많이 사용하기 때문에, 하스켈 프로그램은 보통 동등한 명령형 프로그램보다 짧아요. 그리고 짧은 프로그램은 긴 프로그램보다 유지보수가 쉽고 버그도 적죠.
하스켈은 정말 똑똑한 사람들(박사 학위 소지자들)이 만들었어요. 하스켈 작업은 1987년에 연구자들이 모여 끝내주는 언어를 설계하겠다고 시작되었죠. 2003년에는 언어의 안정된 버전을 정의한 Haskell Report가 발표되었어요.
필요한 건 텍스트 편집기와 하스켈 컴파일러예요. 아마 좋아하는 편집기는 이미 설치되어 있을 테니 그건 생략할게요. 이 튜토리얼에서는 가장 널리 쓰이는 하스켈 컴파일러인 GHC를 사용할 거예요. 가장 좋은 시작 방법은 권장되는 하스켈 설치 관리 도구인 GHCup을 다운로드하는 거예요.
GHC는 하스켈 파일(보통 .hs 확장자)을 받아 컴파일할 수 있을 뿐 아니라, 대화형 모드도 있어요. 파일과 상호작용하며 즉석에서 실험할 수 있죠. 대화형으로요. 로드한 파일의 함수를 호출하면 결과가 곧바로 표시돼요. 학습 용도로는, 매번 컴파일하고 변경한 뒤 프롬프트에서 프로그램을 실행하는 것보다 훨씬 쉽고 빠르답니다. 대화형 모드는 프롬프트에서 ghci라고 입력하면 시작돼요. 예컨대 myfunctions.hs라는 파일에 몇 가지 함수를 정의해 두었다면, :l myfunctions라고 쳐서 그 함수들을 로드한 뒤 곧바로 가지고 놀 수 있어요. 단, myfunctions.hs가 ghci를 실행한 폴더와 같은 곳에 있어야 해요. .hs 파일을 변경했다면 :l myfunctions를 다시 실행하거나, 현재 파일을 다시 로드하는 :r을 써도 같아요. 저는 보통 .hs 파일에 함수를 몇 개 정의하고, 로드해서 갖고 놀다가, 파일을 수정하고, 다시 로드하는 식으로 반복하며 실험해요. 여기서도 바로 그렇게 해 볼 거예요.