소개
Introduction
이 책에 대해
About this tutorial
"유익한 하스켈 배우기"에 오신 것을 환영합니다! 만약 여러분이 이 책을 읽고 계시다면, 아마도 하스켈을 배우고 싶으신 것이겠지요. 네, 맞는 장소에 오셨습니다. 그러나, 우선 이 책에 대해 조금 이야기해보도록 합시다.
Welcome to Learn You a Haskell for Great Good! If you're reading this, chances are you want to learn Haskell. Well, you've come to the right place, but let's talk about this tutorial a bit first.
제가 이 책을 저술하기로 결심한 이유는 하스켈에 대한 제 자신의 앎을 확고히 하고 싶었기 때문이며, 또, 하스켈에 처음이신 분들이 저의 견해로부터 하스켈을 배우실 수 있도록 도울 수 있으리라 생각했기 때문입니다. 인터넷상에 떠다니는 하스켈 입문서는 그다지 많지 않습니다. 제가 하스켈을 처음 시작했을 무렵에, 저는 하나의 자료로만 학습하지 않았습니다. 제가 하스켈을 배운 방식은 몇 개의 다른 입문서과 문서들을 읽는 것이었는데 그 이유는 각각의 자료가 무언가를 다른 책과는 다른 방식으로 설명했기 때문입니다. 여러가지 자료들을 거치면서, 저는 그러한 조각들을 합칠 수 있었고 모든 것이 딱 들어맞게 되었습니다. 그래서, 이것은 하스켈 학습에 대한 또 하나의 유용한 자료를 더하려는 시도이기 때문에 여러분은 여러분이 마음에 들어하시는 자료를 찾을 더 큰 기회를 가지는 것입니다.
I decided to write this because I wanted to solidify my own knowledge of Haskell and because I thought I could help people new to Haskell learn it from my perspective. There are quite a few tutorials on Haskell floating around on the internet. When I was starting out in Haskell, I didn't learn from just one resource. The way I learned it was by reading several different tutorials and articles because each explained something in a different way than the other did. By going through several resources, I was able put together the pieces and it all just came falling into place. So this is an attempt at adding another useful resource for learning Haskell so you have a bigger chance of finding one you like.
이 입문서는 명령형 프로그래밍 언어 (C, C++, Java, Python 등) 에 경험이 있지만, 함수형 언어 (Haskell, ML, OCaml 등) 로는 이전에 프로그래밍을 해보신 적이 없는 분들을 대상으로 합니다. 설령, 여러분이 의미있는 프로그래밍 경험을 전혀 가지고 있지 않다고 하더라도, 장담컨대 여러분과 같은 탁월하신 분들이라면 충분히 이 책을 읽으며 하스켈을 배우실 수 있으리라 생각합니다.
This tutorial is aimed at people who have experience in imperative programming languages (C, C++, Java, Python …) but haven't programmed in a functional language before (Haskell, ML, OCaml …). Although I bet that even if you don't have any significant programming experience, a smart person such as yourself will be able to follow along and learn Haskell.
freenode 네트워크에 있는 #haskell 채널은 여러분이 혹시 잘 이해가 안 되는 부분이 있을 경우 질문하기에 좋은 장소입니다. 이 곳에 있는 사람들은 굉장히 친절하고 관용적이며, 입문자들에게 이해심이 있습니다.
The channel #haskell on the freenode network is a great place to ask questions if you're feeling stuck. People there are extremely nice, patient and understanding to newbies.
저는 하스켈을 마침내 이해하기 전에 약 2번 가량 하스켈 배우기에 실패했습니다. 왜냐하면 모든 것이 저에게는 너무 이상하게 보였으며 저는 이해할 수 없었기 때문입니다. 하지만, 한 순간 "번쩍"하더니, 이 초기의 허들을 뛰어넘고 난 이후에는, 꽤 순조로운 항해였습니다. 아마도 제가 하고 싶은 말은, 하스켈은 훌륭하며 만약 여러분이 프로그래밍에 흥미가 있으시다면 여러분께서는 하스켈이 처음엔 이상해 보일지라도 하스켈을 꼭 배우셔야 한다는 것입니다. 하스켈을 배우는 것은 마치 프로그래밍을 하는 법을 처음으로 배우는 것과 꼭 같습니다. 재밌다는 것이죠! 하스켈은 여러분이 생각을 다르게 하도록 만드는데, 이 사실이 우리를 다음 절으로 이끌고 있습니다.
I failed to learn Haskell approximately 2 times before finally grasping it because it all just seemed too weird to me and I didn't get it. But then once it just "clicked" and after getting over that initial hurdle, it was pretty much smooth sailing. I guess what I'm trying to say is: Haskell is great and if you're interested in programming you should really learn it even if it seems weird at first. Learning Haskell is much like learning to program for the first time — it's fun! It forces you to think differently, which brings us to the next section …
그래서 하스켈이 무엇입니까?
So what's Haskell?
하스켈은 순수한 함수형 언어입니다. 명령형 언어에서 여러분은 컴퓨터에게 일련의 작업들을 제공하고 컴퓨터가 그 작업들을 실행함으로써 문제를 해결합니다. 그 작업들을 실행하는 동안, 컴퓨터는 상태를 변경할 수 있습니다. 예를 들어, 여러분은 변수 a를 5로 설정하고 무언가를 한 다음 이 변수를 다른 값으로 설정합니다. 여러분은 어떤 일을 여러번 하기 위한 제어 흐름 구조를 가지고 있습니다. 순수한 함수형 언어에서 여러분은 이런 식의 무엇을 할 지를 컴퓨터에게 이야기하는 대신 여러분은 무엇이 있는지를 컴퓨터에게 이야기합니다. 어떤 숫자의 팩토리얼은 1부터 그 숫자까지의 모든 숫자들의 곱이고, 숫자들의 리스트의 합은 첫번째 숫자 더하기 나머지 숫자들의 합이고, 이런 식입니다. 여러분은 이를 함수의 형태로 표현합니다. 여러분은 또한 변수를 무언가로 설정한 뒤 나중에 다른 무언가로 설정할 수 없습니다. 만약 여러분이 a가 5라고 한다면, 나중에 이것이 다른 무언가라고 할 수 없는데 그 이유는 여러분은 방금 이것이 5라고 말했기 때문입니다. 거짓말쟁이가 아니니까요! 그래서 순수한 함수형 언어에선, 함수는 부작용을 가지고 있지 않습니다. 함수가 할 수 있는 유일한 것은 무언가를 계산하고 계산값을 결과로써 반환하는 것 밖엔 없습니다. 처음엔, 이것이 일종의 제한으로 보일 수 있습니다만 실제로 이것은 얼마간의 아주 좋은 중요성을 가지고 있습니다: 만약 함수가 같은 매개변수로 두 번 호출된다면, 같은 결과를 반환하는 것이 보장되어 있습니다. 이는 참조 투명성[referential transparency]이라고 불리우며 이는 컴파일러로 하여금 프로그램의 동작에 대해 추론할 수 있게 할 뿐 아니라 여러분으로 하여금 쉽게 함수가 올바르다는 것을 연역하고 (그리고 심지어 증명하고), 단순한 함수들을 접합함으로써 더욱 복잡한 함수들을 제작할 수 있게 합니다.
Haskell is a purely functional programming language. In imperative languages you get things done by giving the computer a sequence of tasks and then it executes them. While executing them, it can change state. For instance, you set variable a to 5 and then do some stuff and then set it to something else. You have control flow structures for doing some action several times. In purely functional programming you don't tell the computer what to do as such but rather you tell it what stuff is. The factorial of a number is the product of all the numbers from 1 to that number, the sum of a list of numbers is the first number plus the sum of all the other numbers, and so on. You express that in the form of functions. You also can't set a variable to something and then set it to something else later. If you say that a is 5, you can't say it's something else later because you just said it was 5. What are you, some kind of liar? So in purely functional languages, a function has no side-effects. The only thing a function can do is calculate something and return it as a result. At first, this seems kind of limiting but it actually has some very nice consequences: if a function is called twice with the same parameters, it's guaranteed to return the same result. That's called referential transparency and not only does it allow the compiler to reason about the program's behavior, but it also allows you to easily deduce (and even prove) that a function is correct and then build more complex functions by gluing simple functions together.
하스켈은 게으릅니다. 이 말의 의미는, 별도로 지시받지 않는 이상, 여러분에게 정말로 결과를 보여주도록 강제되기 전까지는 하스켈은 함수를 호출해 무언가를 계산하지 않을 것임을 의미합니다. 이 성질은 참조 투명성과 잘 부합하며 여러분으로 하여금 프로그램을 일련의 데이터에 대한 변형으로써 생각할 수 있도록 합니다. 이 성질은 또한 무한 데이터 구조와 같은 멋있는 것들을 가능케 합니다. 예를 들어 여러분이 변경할 수 없는[immutable; 불변의] 숫자들의 리스트 xs = [1,2,3,4,5,6,7,8]
와 모든 원소에 2를 곱한 후 새로운 리스트를 반환하는 함수 doubleMe
를 가지고 있다고 합시다. 만약 우리가 명령형 언어에서 이 리스트에 8을 곱하고 싶어서 doubleMe(doubleMe(doubleMe(xs)))
를 했다면, 아마도 리스트를 한 번 통과시키고, 복사본을 만들고, 그리고나서 반환했을 것입니다. 그리고나서 이 리스트를 두번 더 통과시키고 결과를 반환할 것입니다. 게으른 언어에서는, 여러분에게 결과를 보이라는 강제 없이 리스트에 대해 doubleMe
를 호출하면, 프로그램이 "나중에 계산할게요~~~" 라고 말하는 것으로 끝나게 됩니다. 하지만 한 번 여러분이 결과를 보기를 원하기만 하면, 첫번째 doubleMe
는 두번째 doubleMe
에게 당장 결과를 내놓으라고 갈구며, 두번째는 세번째를 다시 갈구고, 세번째는 마지못해 2배가 된 1, 즉 2를 산출합니다. 두번째는 이 결과를 받아 4를 첫번째에게 전달합니다. 첫번째는 이 결과를 보고 여러분께 첫번째 원소는 8이라고 대답합니다. 요컨대 오직 여러분이 결과가 정말 필요한 경우에만 리스트를 한 번 통과시킵니다. 이러한 방식으로 여러분이 게으른 언어로부터 무언가를 원할 때 여러분은 그냥 초기 데이터를 취해서 효과적으로 변형하고 갈고닦아서 마지막에 가서 여러분에 원하는 것과 비슷해지도록 할 수 있습니다.
Haskell is lazy. That means that unless specifically told otherwise, Haskell won't execute functions and calculate things until it's really forced to show you a result. That goes well with referential transparency and it allows you to think of programs as a series of transformations on data. It also allows cool things such as infinite data structures. Say you have an immutable list of numbers xs = [1,2,3,4,5,6,7,8] and a function doubleMe which multiplies every element by 2 and then returns a new list. If we wanted to multiply our list by 8 in an imperative language and did doubleMe(doubleMe(doubleMe(xs))), it would probably pass through the list once and make a copy and then return it. Then it would pass through the list another two times and return the result. In a lazy language, calling doubleMe on a list without forcing it to show you the result ends up in the program sort of telling you "Yeah yeah, I'll do it later!". But once you want to see the result, the first doubleMe tells the second one it wants the result, now! The second one says that to the third one and the third one reluctantly gives back a doubled 1, which is a 2. The second one receives that and gives back 4 to the first one. The first one sees that and tells you the first element is 8. So it only does one pass through the list and only when you really need it. That way when you want something from a lazy language you can just take some initial data and efficiently transform and mend it so it resembles what you want at the end.
하스켈은 정적 타입 언어입니다. 여러분이 프로그램을 컴파일할 때, 컴파일러는 코드의 어떤 부분이 숫자이며 어떤 부분이 문자열인지 등을 알고 있습니다. 이는 여러가지의 잠재적인 오류가 컴파일 타임에 잡힐 수 있음을 의미합니다. 만약 여러분이 숫자와 문자열을 더하려고 시도한다면, 컴파일러는 제자리에 바로 드러누울 것입니다. 하스켈은 타입 추론을 가지고 있는 아주 훌륭한 타입 체계를 사용합니다. 이 말인즉슨 여러분은 타입을 가지고 있는 모든 코드 조각에 명시적으로 레이블을 꼭 달 필요가 없다는 것입니다. 왜냐하면 타입 체계가 그 코드 조각에 대해 많은 것을 총명하게 발혀낼 수 있기 때문입니다. 만약 여러분이 a = 5 + 4
라고 한다면, 여러분은 하스켈에게 a
가 숫자임을 굳이 말할 필요가 없습니다. 하스켈이 혼자서 밝혀낼 수 있기 때문입니다. 타입 추론은 또한 여러분의 코드가 좀 더 일반적이 될 수 있도록 합니다. 만약 여러분이 만든 함수가 두 개의 매개변수를 취해 둘을 더하는데 여러분이 매개변수의 타입을 명시적으로 서술하지 않았다 해도, 함수는 숫자처럼 작동하는 임의의 두 개의 매개변수에 대해 작동할 것입니다.
Haskell is statically typed. When you compile your program, the compiler knows which piece of code is a number, which is a string and so on. That means that a lot of possible errors are caught at compile time. If you try to add together a number and a string, the compiler will whine at you. Haskell uses a very good type system that has type inference. That means that you don't have to explicitly label every piece of code with a type because the type system can intelligently figure out a lot about it. If you say a = 5 + 4, you don't have to tell Haskell that a is a number, it can figure that out by itself. Type inference also allows your code to be more general. If a function you make takes two parameters and adds them together and you don't explicitly state their type, the function will work on any two parameters that act like numbers.
하스켈은 우아하고 간결합니다. 하스켈은 다수의 고수준 개념들을 사용하기 때문에, 하스켈 프로그램들은 통상적으로 같은 일을 하는 명령형 프로그램들에 비해 짧습니다. 그리고 짧은 프로그램은 긴 프로그램에 비해 유지보수하기가 쉬우며 버그가 적습니다.
Haskell is elegant and concise. Because it uses a lot of high level concepts, Haskell programs are usually shorter than their imperative equivalents. And shorter programs are easier to maintain than longer ones and have less bugs.
하스켈은 정말로 똑똑한 분들 (박사학위 보유) 에 의해 개발되었습니다. 하스켈에 대한 작업은 1987년에 연구원들의 위원회가 강력한 언어를 설계하기 위해 모였을 때 시작되었습니다. 2003년에 하스켈 보고서가 출판되었는데, 여기서 하스켈의 안정된 버전을 정의합니다.
Haskell was made by some really smart guys (with PhDs). Work on Haskell began in 1987 when a committee of researchers got together to design a kick-ass language. In 2003 the Haskell Report was published, which defines a stable version of the language.
공부하는데 필요한 것
What you need to dive in
텍스트 편집기와 하스켈 컴파일러. 여러분은 아마도 이미 선호하시는 텍스트 편집기가 설치되어 있을 것이므로, 이 부분에 대해 이야기하지는 않겠습니다. 이 입문서의 목적상 우리는 GHC를 사용할 것인데, 이는 가장 널리 사용되는 하스켈 컴파일러입니다. 시작하기에 가장 좋은 방법은 이 하스켈 플랫폼을 다운로드받는 것인데, 이는 본질적으로 필요한 것들이 다 갖추어진 하스켈입니다.
※ 해당 링크에 들어가보니 2022년자로 더 이상 사용되지 않는[deprecated]다고 나와있습니다. 대신 하스켈 웹사이트의 get started 부분에서 필요한 정보를 찾을 수 있는 것으로 보입니다.
A text editor and a Haskell compiler. You probably already have your favorite text editor installed so we won't waste time on that. For the purposes of this tutorial we'll be using GHC, the most widely used Haskell compiler. The best way to get started is to download the Haskell Platform, which is basically Haskell with batteries included.
GHC는 하스켈 스크립트를 취해서 (하스켈 스크립트는 보통 .hs 확장자를 가지고 있습니다) 컴파일하는 한편 여러분이 "대화식으로" 스크립트와 소통할 수 있게 해주는 인터랙티브 모드 또한 가지고 있습니다. (역주: python처럼 콘솔 창에서 python이라고 치면 나오는 REPL환경같은 느낌인 것 같다) 여러분은 불러온 스크립트로부터 함수를 호출할 수 있으며 결과가 즉시 출력됩니다. 학습 목적상, 코드를 변경할 때마다 매번 컴파일하고 프로그램을 프롬프트에서 실행하는 것보다 더 쉽고 빠릅니다. 인터랙티브 모드는 프롬프트에서 ghci라고 타이핑함으로써 호출될 수 있습니다. 만약 여러분이 예를 들어 myfunctions.hs 라는 파일에 함수를 정의해놓았다면, 여러분은 :l myfunctions
라고 입력해 그 함수들을 로딩한 뒤 그 함수들을 가지고 놀 수 있습니다 (myfunctions.hs가 ghci가 호출된 폴더와 같은 폴더에 있다고 가정했을 때). 만약 여러분이 .hs 스크립트를 변경한다면, :l myfunctions
를 다시 실행하거나 :r
를 실행하면 되는데, 후자는 현재 스크립트를 리로딩하기 때문에 전자와 동일합니다. 뭔가를 가지고 놀 때 저의 경우 통상적인 작업 흐름은 몇 개의 함수를 .hs 파일에 정의하고, 로딩한 뒤, 함수들을 시험해보고, .hs 파일을 변경하고, 다시 로딩하는 것의 반복입니다. 이것이 또한 우리가 이 책에서 하게 될 것입니다.
GHC can take a Haskell script (they usually have a .hs extension) and compile it but it also has an interactive mode which allows you to interactively interact with scripts. Interactively. You can call functions from scripts that you load and the results are displayed immediately. For learning it's a lot easier and faster than compiling every time you make a change and then running the program from the prompt. The interactive mode is invoked by typing in ghci at your prompt. If you have defined some functions in a file called, say, myfunctions.hs, you load up those functions by typing in :l myfunctions and then you can play with them, provided myfunctions.hs is in the same folder from which ghci was invoked. If you change the .hs script, just run :l myfunctions again or do :r, which is equivalent because it reloads the current script. The usual workflow for me when playing around in stuff is defining some functions in a .hs file, loading it up and messing around with them and then changing the .hs file, loading it up again and so on. This is also what we'll be doing here.