Haskell 개발 환경

Haskell 개발 환경

GHC(glasgow haskell compiler)는 Haskell 컴파일러입니다. 2018년 12월 기준 최신 버전은 8.4.4 입니다. 물론 하스켈 컴파일러가 이것만 있는 것은 아니고 다른 컴파일러(Hugs, NHC, JHC, Yhc)도 몇가지 있지만 GHC가 가장 많이 쓰이는 편이다. GHC는 컴파일 뿐만 아니라 대화형 인터프리터(GHCi) 역시 지원한다.

왜 stack을 사용하는가

빌드 도구

요즘 사용되는 대부분의 언어는 빌드 도구를 사용한다. 예를 들어, Scala에선 sbt, Java에서 MavenGradle등이 사용된다. 하스켈도 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라고 부른다. StackageStable 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가 존재하지만, 필자는 VSCodehaskero를 사용한다. 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

srcapp 폴더는 Haskell 코드를 포함하고, src 폴더는 재사용 할 수 있는 프로젝트의 일부를 포함하고 있다. 그리고 app 폴더는 실행을 위한 코드가 포함된다. 만약 웹 응용 프로그램을 만들면 Haskell 코드를 어디에 위치시켜도 크게 상관없을 없지만 대부분의 개발자는 코드를 src 폴더에 넣었고 app 폴더엔 한 줄짜리 함수를 호출하여 응용 프로그램을 실행한다. test 폴더는 그 이름에서 유추할 수 있듯이 테스트 관련 코드를 포함한다. .cabalSetup.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

교재가 필요하신가요?

  • Yorgeycis194 강의부터 보세요. 만약 좀 더 자세한 내용을 필요로 하신다면 그 때 Haskell: the Craft of Functional Programming를 구매하세요.