Dev

Haskell 스터디 Part.01

December 5, 2018

Haskell 스터디 Part.01

개발 환경

Haskell 스터디 Part.01GHC(Glasgow Haskell Compiler)는 Haskell 컴파일러입니다. 2018년 12월 기준 최신 버전은 8.4.4 입니다. 물론 Haskell 컴파일러가 이것만 있는 것은 아니고 다른 컴파일러도 몇 있습니다만 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이 발생하지 않도록 의존성 버전을 고정한 패키지의 집합을 모아두고 관리합니다. 그것이 Stackageltsnightly 등의 이름을 가진 스냅샷(snapshot)입니다. 즉, 의존 관계가 손상되지 않도록 하스켈 라이브러리 제작자,Stackage 메인테이너가 노력하고 있습니다.

stack이 등장하기 전에는 Haskell Platform을 사용하는 경우가 많았습니다. 컴파일러(GHC), 빌드 도구(cabal) 자주 사용하는 패키지(text, bytestring)를 한 번에 설치할 수 있었기 때문입니다. 그러나 현재는 거의 사용하지 않습니다. 하스켈을 계속해서 공부하거나 사용하고자 하는 분들이라면 stack을 사용하길 권장합니다. 대부분의 프로젝트가 stack으로 관리되고 있기 때문에 stack에 익숙해지는 것을 다시 한번 추천드립니다.

결론적으로 말해서 stack은 의존성 문제가 없는 패키지 리스트를 뽑아서 배포하고자 만들었습니다(자료 구조 할 때의 그 스택은 아닙니다. 사실 이게 널리 쓰이는 용어라 이름 가지고 얘기가 좀 있긴 했습니다). 하스켈의 중앙 패키지 저장소는 Hackage라고 불리고, Stack에서 쓰이는 패키지 리스트 저장소는 Stackage라고 부릅니다. StackageStable Hackage의 약자로, 어떤 조합으로도 종속성 오류가 일어나지 않도록 모아둔 패키지 집합(혹은 스냅샷)을 제공합니다. 스냅 샷은 2가지 종류가 있습니다. 3~6개월 기간으로 관리되는 장기 지원(lts, Long Term Support), 하루 하루 관리되는 nightly 버전입니다. 스냅샷의 버전 규칙은 아래와 같습니다.

  • lts 버전은 X.Y 형식으로 되어 있으며, X는 메이저 버전, Y는 마이너 버전 입니다.
  • nightly 버전은 nightly-YYYY-MM-DD 형식으로 제공됩니다.

예를 들어, lts-10.0 → lts-11.0로 메이저 버전이 변경되면 패키지 추가 및 제거가 이뤄집니다. lts-11.6 → lts-11.7와 같이 일요일에 이뤄지는 마이너 버전 변경은 호환되는 패키지가 추가되거나 업데이트 됩니다. 마이너 버전 변경의 경우 코드에 문제가 발생할 여지가 거의 없습니다. 호환성을 유지하면서 새로운 기능을 사용할 수 있습니다.

다만 Stack이 2015년 중순부터 나오기 시작한 패키지 매니저라서, 간혹 Hackage에 있지만 Stackage에는 없는 패키지이거나 소스에서 stack.yaml을 지원하지 않는 패키지의 경우 Stack을 쓰지 못하고 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 에서 사용 가능한 옵션이 출력됩니다.

스냅샷 지정

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 폴더는 그 이름에서 유추할 수 있듯이 테스트 관련 코드를 포함합니다.

hauth.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: [email protected]
   github-username: sigmadream

교재가 필요하신가요?

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