Haskell 개발 환경
GHC
(glasgow haskell compiler)는 Haskell
컴파일러입니다. 2018년 12월 기준 최신 버전은 8.4.4
입니다. 물론 하스켈 컴파일러가 이것만 있는 것은 아니고 다른 컴파일러(Hugs
, NHC
, JHC
, Yhc
)도 몇가지 있지만 GHC가 가장 많이 쓰이는 편이다. GHC는 컴파일 뿐만 아니라 대화형 인터프리터(GHCi
) 역시 지원한다.
왜 stack을 사용하는가
빌드 도구
요즘 사용되는 대부분의 언어는 빌드 도구를 사용한다. 예를 들어, Scala
에선 sbt
, Java
에서 Maven
과 Gradle
등이 사용된다. 하스켈도 stack
이란 빌드 도구를 사용한다. 꼭 빌드 도구를 사용할 필요는 없지만 하스켈에서 본격적인 응용 프로그램을 개발할 때 빌드 도구 없이 개발하는 것은 매우 어렵다. 개발이 가능하더라도 프로젝트의 규모가 성장함에 따라 빌드과정이 복잡해지고, 심지어 특정 환경에서 빌드가 불가능할 때도 있다.
Cabal과 Stack
stack 이전에 사용하던 빌드 도구 중 가장 유명하고 많은 사용자를 가지고 있던 cabal
의 경우 cabal hell
이라는 패키지 의존성 문제가 발생하곤 했다. 여러 개의 하스켈 프로젝트를 빌드하려고 하면, 다른 프로젝트의 빌드가 안되는 경우가 존재합니다. 특히 Yesod
등과 같이 비교적 큰 라이브러리의 경우 cabal hell
을 피해갈 방법이 없었다. 이 문제를 해결하기 위해 2015년에 stack이 등장한다.
하스켈 역사에서 볼 때 비교적 '아주' 최근의 일입니다. stack은 cabal을 개선하였다. 기존의 cabal을 이용하면서 그 위에 cabal hell
이 발생하지 않도록 의존성 버전을 고정한 패키지의 집합을 모아두고 관리한다. 즉, 의존 관계가 손상되지 않도록 하스켈 라이브러리 제작자, stack의 메인테이너가 노력하고 있다.
stack이 등장하기 전에는 Haskell Platform
을 사용하는 경우가 많았다. 컴파일러(GHC
), 빌드 도구(cabal
) 자주 사용하는 패키지(text
, bytestring
)를 한 번에 설치할 수 있었기 때문이다. 그러나 현재는 거의 사용하지 않는다. 하스켈을 계속해서 공부하거나 사용하고자 하는 분들이라면 stack을 사용하길 권장한다. 대부분의 프로젝트가 stack으로 관리되고 있기 때문에 stack에 익숙해지는 것을 다시 한번 추천한다.
결론적으로 말해서 stack은 의존성 문제가 없는 패키지 리스트를 뽑아서 배포하고자 만들었다(자료 구조 할 때의 그 스택은 아닙니다. 사실 이게 널리 쓰이는 용어라 이름 가지고 얘기가 좀 있긴 했다). 하스켈의 중앙 패키지 저장소는 Hackage
라고 불리고, Stack에서 쓰이는 패키지 리스트 저장소는 Stackage
라고 부른다. Stackage
는 Stable Hackage
의 약자로, 어떤 조합으로도 종속성 오류가 일어나지 않도록 모아둔 패키지 집합(혹은 스냅샷)을 제공한다. 스냅 샷은 2가지 종류가 있는데, 3~6개월 기간으로 관리되는 장기 지원(lts
, Long Term Support), 하루 하루 관리되는 nightly
버전이다. 스냅샷의 버전 규칙은 a) lts 버전은 X.Y 형식으로 되어 있으며, X는 메이저 버전, Y는 마이너 버전, b) nightly 버전은 nightly-YYYY-MM-DD
형식으로 제공된다. 예를 들어, lts-10.0 → lts-11.0
로 메이저 버전이 변경되면 패키지 추가 및 제거가 이뤄진다. lts-11.6 → lts-11.7
와 같이 일요일에 이뤄지는 마이너 버전 변경은 호환되는 패키지가 추가되거나 업데이트 된다. 마이너 버전 변경의 경우 코드에 문제가 발생할 여지가 거의 없고, 호환성을 유지하면서 새로운 기능을 사용할 수 있다.
다만 Stack이 2015년 중순부터 나오기 시작한 패키지 매니저라서, 간혹 Hackage에 있지만 Stackage에는 없는 패키지이거나 소스에서 stack.yaml을 지원하지 않는 패키지의 경우 cabal을 사용해야 한다.
stack 설치
*NIX
기반의 경우 설치 파일 아래와 같이 터미널 명령어를 사용해서 설치할 수 있으며, 윈도우의 경우 The Haskell Tool Stack
에서 제공하는 바이너리 파일로 설치하면 된다.
curl -sSL https://get.haskellstack.org/ | sh
wget -qO- https://get.haskellstack.org/ | sh
설치 후 stack을 설치한 곳을 PATH
에 넣어주면 설치가 완료됩니다.
echo 'export PATH=~/.local/bin:$PATH' >> ~/.zshrc
설치에 대한 자세한 사항을 알고 싶다면 이 곳을 참고하면 된다.
스냅샷 지정
stack에서 특정 스냅샷을 사용해야 할 경우 아래와 같이 스냅샷을 지정할 수 있다.
stack ghci --resolver nightly
stack ghci --resolver lts
자신이 원하는 스냅샷 버전을 사용해야 할 경우 아래와 같이 선택적으로 전달할 수 있다.
$ stack repl --resolver lts-11.7
Downloaded lts-11.7 build plan.
Building all executables for `PFAD' once. After a successful build of all of them, only specified executables will be rebuilt.
PFAD-0.1.0.0: configure (lib + exe)
Configuring PFAD-0.1.0.0...
clang: warning: argument unused during compilation: '-nopie' [-Wunused-command-line-argument]
PFAD-0.1.0.0: initial-build-steps (lib + exe)
The following GHC options are incompatible with GHCi and have not been passed to it: -threaded
Configuring GHCi with the following packages: PFAD
Using main module: 1. Package `PFAD' component exe:PFAD-exe with main-is file: /Users/sigmadream/Works/PFAD/app/Main.hs
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling Lib ( /Users/sigmadream/Works/PFAD/src/Lib.hs, interpreted )
[2 of 2] Compiling Main ( /Users/sigmadream/Works/PFAD/app/Main.hs, interpreted )
Ok, two modules loaded.
Loaded GHCi configuration from /private/var/folders/xb/lg7_c3752lq67cc4szpvwn6c0000gn/T/haskell-stack-ghci/c1241fc4/ghci-script
*Main Lib>
또한 위에서 소개하지 않지만 GHC
버전도 지정 가능하다.
$ stack ghci --resolver ghc-8.4.2
Preparing to install GHC to an isolated location.
This will not interfere with any system-level installation.
Downloaded ghc-8.4.2.
Unpacking GHC into /Users/sigmadream/.stack/programs/x86_64-osx/ghc-8.4.2.temp/ ...
사용 가능한 스냅 샷 목록은 $ stack ls snapshots
명령으로 확인할 수 있다.
$ stack ls snapshots
lts-10.0
lts-10.2
lts-11.10
lts-11.13
lts-11.15
lts-11.7
lts-12.2
lts-3.0
lts-8.14
lts-9.21
(END)
Stack을 사용한 프로젝트 생성
간단하게 프로젝트를 생성하고 프로젝트를 실행하는 방법은 아래와 같다.
stack new htest
cd htest
stack setup
stack build
stack exec htest-exe
주의할 점은 ghc
, ghci
, runhaskell
등은 stack의 커맨드로 실행해야 한다는 점이다. ghci가 아닌 stack ghci
로 GHCi를 실행시켜야 하고, ghc -O2 Main.hs
가 아닌 stack ghc -- -O2 Main.hs
로 커맨드를 실행해야 한다. 좀 더 자세한 내용은 영상을 참고하자.
Haskell 개발 환경
다양한 IDE
가 존재하지만, 필자는 VSCode
와 haskero
를 사용한다. OS X
뿐만 아니라 Windows
, Linux
등에서 모두 사용 가능하기 때문에 손쉽게 시작할 수 있다.
brew cask install visual-studio-code
VSCode에서 사용할 'haskero'는 VSCode의 플러그인(extensions)에서 'haskero'를 설치 후 재시작하고, stack을 사용하여 intero
를 설치하기 위해선 커맨드라인에 stack build intero
명령어를 실행하면 된다.
stack build intero
Haskell 프로젝트 구조
.
├── ChangeLog.md
├── LICENSE
├── README.md
├── Setup.hs
├── app
│ └── Main.hs
├── htest.cabal
├── package.yaml
├── src
│ └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
src
와 app
폴더는 Haskell 코드를 포함하고, src 폴더는 재사용 할 수 있는 프로젝트의 일부를 포함하고 있다. 그리고 app 폴더는 실행을 위한 코드가 포함된다. 만약 웹 응용 프로그램을 만들면 Haskell 코드를 어디에 위치시켜도 크게 상관없을 없지만 대부분의 개발자는 코드를 src 폴더에 넣었고 app 폴더엔 한 줄짜리 함수를 호출하여 응용 프로그램을 실행한다. test
폴더는 그 이름에서 유추할 수 있듯이 테스트 관련 코드를 포함한다. .cabal
과 Setup.hs
는 Haskell의 빌드 도구인 cabal에서 사용하며, stack.yaml
은 stack에 관한 내용을 설정하는 파일이다.
프로젝트 설정
hauth.cabal
에 대해 자세히 살펴보자. 앞에 간단히 언급했듯이, .cabal
파일은 빌드에 필요한 설정을 담고있다. 예를 들어, 프로젝트가 의존하는 패키지등을 정의한다.
library
exposed-modules: Lib
other-modules: Paths_htest
hs-source-dirs: src
build-depends: base >=4.7 && <5
default-language: Haskell2010
library
섹션에서는 프로젝트에 필요한 라이브러리에 관련되 내뇽을 정의한다. hs-source-dirs
는 프로젝트의 소스 코드가 위치한 곳을 정의하고, exposed-modules
는 사용자가 사용할 수 있는 외부 모듈을 설정한다. build-depends
는 우리가 의존하는 외부 라이브러리를 정의하며 base >= 4.7 && < 5
와 같이 외부 라이브러리의 버전을 정의(4.7이상 5미만)한다.
사용시 주의사항
설정 파일의 포멧을 관리하기 위해서 stack은 hpack
을 사용한다. hpack을 사용하면 package.yaml
을 기준으로 .cabal
파일을 자동으로 생한다. 또한 hpack은 stack을 설치할 때 기본으로 설치되기 때문에 별도로 신경 쓸 필요는 없지만 .cabal
파일을 수동으로 관리할 때 주의해야 한다.
특히 수동으로 .cabal
파일을 변경할 경우 hpack이 작동하지 않기 때문에 개별적으로 .cabal
을 수동으로 수정하지 않도록 주의해야 하며, 배포할 때 .cabal
은 제외하고 배포할 수 있도록 해야 한다. stack을 사용해서 프로젝트를 생성할 때 .gitignore
에 <projectName>.cabal
파일이 포함되어 있기 때문에 별다른 문제는 없지만, .gitignore
을 개인적인 템플릿 형태로 사용하시는 분들은 주의해야 한다.
Tip
$HOME/.stack/config.yaml
파일을 수정하면 stack new
를 사용하면 해당 항목이 처리되어 있음을 확인할 수 있다.
default-template: new-template
templates:
scm-init: git
params:
author-name: Sangkon Han
author-email: sigmadream@gmail.com
github-username: sigmadream