I spent some time today getting my emacs config set up to learn Haskell, and ran into a few issues; I figured I'd go ahead and document the process here for everyone's enjoyment. We're going to install and configure Haskell mode, then add a few extensions that'll make learning Haskell fun and easy!
I'm currently running haskell-mode for emacs, with the hs-lint
plugin, Haskell support for FlyMake (which provides on-the-fly syntax checking from the Haskell compiler), and code autocompletion. The steps covered by this tutorial are:
hs-lint
and autocompletion)
Before any of this Emacs jazz, we have to get Haskell, of course. The easiest way to do is to download the Haskell Platform, a "Batteries Included" package of the Glasgow Haskell Compiler.
The Haskell Mode for Emacs page at the Haskell wiki is a nice place to start your explorations, and describes how to install haskell-mode. I found it easier to use ELPA, the Emacs Lisp Package Archive (install instructions here). If you're using the Emacs starter kit, you've already got ELPA.
Once elpa's all set, install haskell-mode
with the following:
M-x package-list-packages
in Emacs
i
by haskell-mode
x
to the start the install.
Before we get into linting or any other customizations, Add the following to your emacs config (~/.emacs
, or ~/.emacs.d/init.el
, if you're using the Starter kit):
(add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode) ;; hslint on the command line only likes this indentation mode; ;; alternatives commented out below. (add-hook 'haskell-mode-hook 'turn-on-haskell-indentation) ;;(add-hook 'haskell-mode-hook 'turn-on-haskell-indent) ;;(add-hook 'haskell-mode-hook 'turn-on-haskell-simple-indent) ;; Ignore compiled Haskell files in filename completions (add-to-list 'completion-ignored-extensions ".hi")
At this point, you should be able to start using Haskell in emacs. Let's write our first function.
test.hs
.
C-c C-z
to get to the Haskell REPL, supplied by inf-haskell mode
test.hs
:
module Examples where square :: Integral a => a -> a square x = x * x
Then type C-c C-l
in that buffer to load the file's contents into the REPL. C-c C-z
over to the repl again and try it out:
*Main> square 10 100 *Main> square 34 1156
Nice.
Next, we're going to add support for Flymake, the emacs syntax-checker. This is a piece of Emacs functionality that'll run our Haskell files through the compiler every few seconds and associate any warnings and errors with some specific line in our Haskell file.
While we're at it, we'll configure hs-lint
, which we can run periodically on our Haskell files for a list of hints as to how to structure code better. For example, running hs-lint
on a Haskell file containing times2 x = (*) 2 x
yields a buffer with:
hlint /Users/sritchie/test.hs /Users/sritchie/test.hs:1:1: Error: Eta reduce Found: times2 x = (*) 2 x Why not: times2 = (*) 2 1 suggestion
Definitely helpful in the learning process.
Flymake depends on an external perl script, hslint
, to pass the contents of the current buffer into the Haskell compiler.
hslint
from this gist and place it somewhere on your path.
chmod a+x hslint
to give the file executable privileges
.emacs
:
(defun flymake-haskell-init () "When flymake triggers, generates a tempfile containing the contents of the current buffer, runs `hslint` on it, and deletes file. Put this file path (and run `chmod a+x hslint`) to enable hslint: https://gist.github.com/1241073" (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list "hslint" (list local-file)))) (defun flymake-haskell-enable () "Enables flymake-mode for haskell, and sets <C-c d> as command to show current error." (when (and buffer-file-name (file-writable-p (file-name-directory buffer-file-name)) (file-writable-p buffer-file-name)) (local-set-key (kbd "C-c d") 'flymake-display-err-menu-for-current-line) (flymake-mode t))) ;; Forces flymake to underline bad lines, instead of fully ;; highlighting them; remove this if you prefer full highlighting. (custom-set-faces '(flymake-errline ((((class color)) (:underline "red")))) '(flymake-warnline ((((class color)) (:underline "yellow")))))
Now, let's add autocompletion. Autocomplete mode is awesome; it provides IDE-like word tab completion of words based on info in open buffers, and some knowledge of the modes of the emacs buffer you're currently working in. In Haskell, we'll get autocompletion of every function we define, plus help with core language constructs. Head over to Auto Complete Mode to download the package, and install with the following:
M-x load-file
<autocomplete-root>/etc/install.el
That should get you all set for the next step.
~/.emacs.d
. (hs-lint
is our linter, of course, and haskell-ac.el
provides autocomplete mode with some knowledge of a few core Haskell constructs.
.emacs
file:
(require 'hs-lint) ;; https://gist.github.com/1241059 (require 'haskell-ac) ;; https://gist.github.com/1241063 (defun my-haskell-mode-hook () "hs-lint binding, plus autocompletion and paredit." (local-set-key "\C-cl" 'hs-lint) (setq ac-sources (append '(ac-source-yasnippet ac-source-abbrev ac-source-words-in-buffer my/ac-source-haskell) ac-sources)) (dolist (x '(haskell literate-haskell)) (add-hook (intern (concat (symbol-name x) "-mode-hook")) 'turn-on-paredit))) (eval-after-load 'haskell-mode '(progn (require 'flymake) (push '("\\.l?hs\\'" flymake-haskell-init) flymake-allowed-file-name-masks) (add-hook 'haskell-mode-hook 'flymake-haskell-enable) (add-hook 'haskell-mode-hook 'my-haskell-mode-hook)))
The above code binds C-c l
to hs-lint
inside of Haskell buffers, and configures a large number of Haskell keywords for autocompletion. Go and test it out inside of test.hs
; you should find that square
autocompletes when you begin typing.
Now go ahead and add the following line to test.hs
:
face :: Int -> Bool
Upon save, or within seconds, you should see an angry red underline. Move the cursor over that line and type C-c d
, and you'll see a tooltip with the following text:
1: The type signature for `face' lacks an accompanying binding.
Adding this will clear things up:
face x = 5 < x
I hope this helped those of you looking to get started exploring Haskell! Please let me know in the comments if anything could be clearer; I'll be posting more down the road, and all requests are welcome.