Personal tools

Haskell mode for Emacs

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
(note on :etags)
Line 11: Line 11:
 
Insert in your ~/.emacs or appropriate file:
 
Insert in your ~/.emacs or appropriate file:
   
<code>
+
<code>(load "/path/to/haskell-mode/haskell-site-file")</code>
(add-to-list 'load-path "path/to/haskell-mode")
 
</code>
 
 
<code>(load "haskell-site-file")</code>
 
 
<code>(add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode) </code>
 
 
<code>(add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
 
</code>
 
 
<code>;; enable auto-mode selection:</code>
 
 
<code>(add-to-list 'auto-mode-alist '("\\.hs\\'" . haskell-mode))</code>
 
   
 
==Tips and use==
 
==Tips and use==
Line 40: Line 40:
 
You could also achieve the same effect by selecting the region and typing <code>C-c C-.</code>
 
You could also achieve the same effect by selecting the region and typing <code>C-c C-.</code>
   
You can also use Haskell-Mode to load Emacs buffers with Haskell code in either Hugs or GHC. To load something in Hugs or ghci, type <code>C-c C-l</code> to load the file. Then, you can go on to type <code>C-c C-r</code> to reload the current module when you have made a change.
+
You can also use Haskell-Mode to load Emacs buffers with Haskell code in either Hugs or GHC. To load something in Hugs or ghci, type <code>C-c C-l</code> to load the file. Then, you can go on to type <code>C-c C-r</code> to reload the current module when you have made a change.
   
 
If you have imenu (standard in Emacs and available as a package in XEmacs), you can use <code>M-x imenu</code> to jump to any definition in the file. A good keybinding for it is:
 
If you have imenu (standard in Emacs and available as a package in XEmacs), you can use <code>M-x imenu</code> to jump to any definition in the file. A good keybinding for it is:
 
<pre-lisp>
 
<pre-lisp>
 
(global-set-key [(control meta down-mouse-3)] 'imenu)
 
(global-set-key [(control meta down-mouse-3)] 'imenu)
  +
</pre-lisp>
  +
You can also make a menu entry for it with
  +
<pre-lisp>
  +
(add-hook 'haskell-mode-hook 'imenu-add-menubar-index)
 
</pre-lisp>
 
</pre-lisp>
 
If you don't have imenu but have func-menu you can do
 
If you don't have imenu but have func-menu you can do
Line 56: Line 60:
 
Debian maintains a [http://bugs.debian.org/cgi-bin/pkgreport.cgi?pkg=haskell-mode list of bugs] for haskell-mode, they should be reported there.
 
Debian maintains a [http://bugs.debian.org/cgi-bin/pkgreport.cgi?pkg=haskell-mode list of bugs] for haskell-mode, they should be reported there.
   
===Xemacs===
+
===XEmacs===
On some the linux systems with XEmacs, admittedly, only verified on Ubuntu and Debian, there is a system function missing that interferes with automatic indenting. Secondly, there seems to be an issue with setting the <code-lisp>haskell-default-face</code-lisp> to <code-lisp>nil</code-lisp>.
+
On some the GNU/Linux systems with XEmacs, admittedly, only verified on Ubuntu and Debian, there is a system function missing that interferes with automatic indenting. Secondly, there seems to be an issue with setting the <code-lisp>haskell-default-face</code-lisp> to <code-lisp>nil</code-lisp>.
   
 
====line-end-position====
 
====line-end-position====
Line 105: Line 109:
   
 
=== Getting set up ===
 
=== Getting set up ===
First you need to load inf-haskell.el. Make sure you don't load haskell-ghci or haskell-hugs; inf-haskell replaces both of them. To do this, add the following to your .emacs:
+
inf-haskell.el is already setup as part of the haskell-mode package, so there is nothing special to do for it. To use the following functions, first find a .hs file, then hit C-c C-l (inferior-haskell-load-file). This fires up Hugs or Ghci (you can change this by customising haskell-program-name) on your file. If this proceeds without errors, you'll be able to use the functions below.
 
(require 'inf-haskell)
 
 
Put the point after this this and hit C-x C-e to load inf-haskell.el. From now on, it'll automatically be started when you start Emacs. To use the following functions, first find a .hs file, then hit C-c C-l (inferior-haskell-load-file). This fires up Hugs (by default; you can change this by customising haskell-program-name) on your file. If this load without errors, you'll be able to use the functions below.
 
   
 
=== inferior-haskell-type (C-c C-t) ===
 
=== inferior-haskell-type (C-c C-t) ===
Line 114: Line 118:
 
Perhaps you've forgotten the order of arguments to foldr. It's easily done; I can never remember whether the operation or final value comes first. That's easy to check: just put your point between the 'f' and 'r' of 'foldr' and hit C-c C-t RET. The type of foldr will be revealed in the echo area. This isn't particularly impressive; haskell-doc.el already did this. However, this will work for ''any'' function in the module in question ''or'' in those modules imported by the current module (including the standard libs)!
 
Perhaps you've forgotten the order of arguments to foldr. It's easily done; I can never remember whether the operation or final value comes first. That's easy to check: just put your point between the 'f' and 'r' of 'foldr' and hit C-c C-t RET. The type of foldr will be revealed in the echo area. This isn't particularly impressive; haskell-doc.el already did this. However, this will work for ''any'' function in the module in question ''or'' in those modules imported by the current module (including the standard libs)!
   
If you find that the type shown in the echo area is overwritten after a short amount of time, turn off haskell-doc-mode by adding the following to your .emacs:
+
If you find that the type shown in the echo area is overwritten after a short amount of time (or any other such problem, of course), please report it as a bug. We know of no such bug, but someone apparently bumped into some such problem which he says he worked around by disabling doc-mode and decl-scan:
   
(add-hook 'haskell-mode-hook 'turn-off-haskell-doc-mode)
+
To turn off haskell-doc-mode, add the following to your .emacs:
+
<pre-lisp>
You'll also need to turn off haskell-decl-scan. The haskell-decl-scan.el file says that it provides a turn-off-haskell-decl-scan, but this isn't true. I turned it off by using the Customize options for the Haskell group and just setting everything to nil in the Haskell Doc subgroup.
+
(remove-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)
  +
</pre-lisp>
  +
To turn off haskell-decl-scan, just refrain from turning it on (it's not enabled by default).
   
 
(P.S. I re-use haskell-doc-mode to save queried type info, and re-display it in the minibuffer. Disabling doc mode would disable that. -- mrd)
 
(P.S. I re-use haskell-doc-mode to save queried type info, and re-display it in the minibuffer. Disabling doc mode would disable that. -- mrd)

Revision as of 23:16, 1 May 2007

haskell-mode is a major mode for Emacs and XEmacs specifically for writing Haskell code. You can get HaskellMode from the web page: http://www.haskell.org/haskell-mode/ or on Debian you can type apt-get install haskell-mode.

Contents

1 Obtaining the CVS version

cvs -d :pserver:anoncvs@cvs.haskell.org:/cvs login # password 'cvs'

cvs -d :pserver:anoncvs@cvs.haskell.org:/cvs co fptools/CONTRIB/haskell-modes/emacs

2 Minimal setup

Insert in your ~/.emacs or appropriate file:

(load "/path/to/haskell-mode/haskell-site-file")

3 Tips and use

Handy keybindings in haskell-mode. See the documentation C-h m for more information:

  • C-c C-= inserts an = sign and lines up type signatures and other pattern matches nicely.
  • C-c C-| inserts a guard
  • C-c C-o inserts a guard
    | otherwise =
    and lines up existing guards
  • C-c C-w inserts a where keyword
  • C-c C-. aligns code over a region in a "sensible" fashion.

now in version 2.2:

  • C-c C-t gets :type for symbol at point, and remembers it
  • C-u C-c C-t inserts a type annotation, for symbol at point, on the line above
  • C-c C-i gets :info for symbol at point
  • C-c M-. find definition of (interpreted) symbol at point

Here's an example for C-c C-=. Put your cursor after myInt and hit C-c C-=

blah :: Int -> Int
blah myInt

note how the function signature is reindented to match the column of the = sign.

blah       :: Int -> Int
blah myInt =

You could also achieve the same effect by selecting the region and typing C-c C-.

You can also use Haskell-Mode to load Emacs buffers with Haskell code in either Hugs or GHC. To load something in Hugs or ghci, type C-c C-l to load the file. Then, you can go on to type C-c C-r to reload the current module when you have made a change.

If you have imenu (standard in Emacs and available as a package in XEmacs), you can use M-x imenu to jump to any definition in the file. A good keybinding for it is:

(global-set-key [(control meta down-mouse-3)] 'imenu)

You can also make a menu entry for it with

(add-hook 'haskell-mode-hook 'imenu-add-menubar-index)

If you don't have imenu but have func-menu you can do

(add-hook 'haskell-mode-hook 'turn-on-haskell-decl-scan)

after which some keybindings will be available, such as:

  • C-c l fume-list-functions - quickly jump to a function definition in this file.

4 Bugs

Debian maintains a list of bugs for haskell-mode, they should be reported there.

4.1 XEmacs

On some the GNU/Linux systems with XEmacs, admittedly, only verified on Ubuntu and Debian, there is a system function missing that interferes with automatic indenting. Secondly, there seems to be an issue with setting the haskell-default-face to nil.

4.1.1 line-end-position

To fix this, find where the haskell mode package is installed on your system. (Usually /usr/share/emacs/site-lisp/haskell-mode). Edit the file haskell-indent.el and add the lines:

(eval-and-compile

  ;; If `line-end-position' isn't available provide one.
  (unless (fboundp 'line-end-position)
    (defun line-end-position (&optional n)
      "Return the `point' of the end of the current line."
      (save-excursion
        (end-of-line n)
        (point))))

right after the comments at the top. That should fix the issue.

4.1.2 haskell-default-face

This one shows up when typing in code (at various spots - most often when typing a qualified function, such as
List.map
.)

To fix this one, edit the file haskell-font-lock.el. Look for the line that says:

(defvar haskell-default-face nil)

and change this to

(defvar haskell-default-face 'default)

In my version, this is line 168.

Then, look for the line that says:

 (,qvarid 0 haskell-default-face)

and change it to

 (,qvarid 0 (symbol-value 'haskell-default-face))

For me, this is line 326 of the file. YMMV - hope this helps.

5 inf-haskell.el: the best thing since the breadknife

inf-haskell.el is _awesome_. At one point I decided to sit down and write a list of functions I'd love to have in haskell-mode, intending to write them myself. I thought I'd check to see whether the key shortcuts I'd chosen were free but I was surprised to find that every one of these functions is already provided by inf-haskell.el! Here's a selection of the highlights:

5.1 Getting set up

inf-haskell.el is already setup as part of the haskell-mode package, so there is nothing special to do for it. To use the following functions, first find a .hs file, then hit C-c C-l (inferior-haskell-load-file). This fires up Hugs or Ghci (you can change this by customising haskell-program-name) on your file. If this proceeds without errors, you'll be able to use the functions below.

5.2 inferior-haskell-type (C-c C-t)

Say you have the following code, which I've just pulled from the Cabal source:

foo = foldr (+) 0 [1..20]

Perhaps you've forgotten the order of arguments to foldr. It's easily done; I can never remember whether the operation or final value comes first. That's easy to check: just put your point between the 'f' and 'r' of 'foldr' and hit C-c C-t RET. The type of foldr will be revealed in the echo area. This isn't particularly impressive; haskell-doc.el already did this. However, this will work for any function in the module in question or in those modules imported by the current module (including the standard libs)!

If you find that the type shown in the echo area is overwritten after a short amount of time (or any other such problem, of course), please report it as a bug. We know of no such bug, but someone apparently bumped into some such problem which he says he worked around by disabling doc-mode and decl-scan:

To turn off haskell-doc-mode, add the following to your .emacs:

(remove-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)

To turn off haskell-decl-scan, just refrain from turning it on (it's not enabled by default).

(P.S. I re-use haskell-doc-mode to save queried type info, and re-display it in the minibuffer. Disabling doc mode would disable that. -- mrd)

5.3 inferior-haskell-info (C-c C-i)

The :info command in GHCi/Hugs is extremely useful; it'll tell you:

  • The definition of an algebraic datatype given its name. E.g. try :info Bool. The output will contain something like data Bool = True | False.
  • The classes a type instantiates given the type's name. :info Bool will also give you the classes Bool instantiates. If you can't see an instance you think should be there, make sure the module where that instance is declared is loaded.
  • The type of a function, given its name.
  • The types of the methods of a class, and the number of arguments of that class, given the class name.
  • The expansion of a type synonym given that synonym's name.

And for all of the above, :info will also tell you the filename and line where that thing is defined. inferior-haskell-info lets you hook into this power. Use it with C-c C-i on anything within a Haskell file.

5.4 inferior-haskell-find-definition (C-c M-.)

This one needs little explanation. Sometimes you just need to find the source of a function, or datatype, or class, or type synonym etc. to see how it works, and this function lets you do just that. Unfortunately, it won't work on the standard lib modules or anything that isn't 'local' to your project. This is one of the most useful functions inf-haskell.el provides.

(Basically, it only works on interpreted code, for which ghci has location information. If you want a more general find-definition, use hasktags to create a TAGS file and then use the normal emacs M-. with that. -- mrd)

Note that you can also create a TAGS file using GHCi's :etags command. DavidHouse 14:38, 29 April 2007 (UTC)
Again, :etags/:ctags only works for interpreted code.

6 Tricks and tweaks

6.1 Automatic unit testing

Here's a cute trick I've evolved:

I'm a great fan of unit test first, as described by eXtremeProgramming on TheOriginalWiki.

With the code below, I can press F12 and immediately run all of my unit tests, and immediately see whether they all passed or not. I've put all of my unit tests into their own file with a main function that runs the tests and gives an exitcode according to the test results. I've specified that the compile-command for that file compiles and runs the file.

This elisp code will run the compile command from the F12 key in emacs. The output will popup a new window twelve lines tall. If the compilation is successful (exitcode zero) the window goes away. If the exitcode is 1 or greater, the window stays so you can see the output.

(require 'compile)

;; this means hitting the compile button always saves the buffer
;; having to separately hit C-x C-s is a waste of time
(setq mode-compile-always-save-buffer-p t)
;; make the compile window stick at 12 lines tall
(setq compilation-window-height 12)

;; from enberg on #emacs
;; if the compilation has a zero exit code, 
;; the windows disappears after two seconds
;; otherwise it stays
(setq compilation-finish-function
      (lambda (buf str)
        (unless (string-match "exited abnormally" str)
          ;;no errors, make the compilation window go away in a few seconds
          (run-at-time
           "2 sec" nil 'delete-windows-on
           (get-buffer-create "*compilation*"))
          (message "No Compilation Errors!"))))


;; one-button testing, tada!
(global-set-key [f12] 'compile)


This Haskell code has some Emacs local variable settings at the bottom specifying what the compile-command should be for this buffer.

import HUnit
import System
 
myTestList = 
    TestList [
              "add numbers" ~: 5 ~=? (3 + 2)
             ,"add numbers" ~: 5 ~=? (3 + 3)
             ]
 
h = runTestTT myTestList
 
main = do c <- h
          putStr $ show c
          let errs = errors c
              fails = failures c
          System.exitWith (codeGet errs fails)
 
codeGet errs fails
 | fails > 0       = ExitFailure 2
 | errs > 0        = ExitFailure 1
 | otherwise       = ExitSuccess
 
-- Local Variables:
-- compile-command: "ghc --make -o Test_Demo -i/home/shae/src/haskell/libraries/ HUnitDemo.hs && ./Test_Demo"
-- End:


If you have any questions, ideas, or suggestions for this code, the maintainer would love to hear them.

6.2 Hoogle integration

Grab Hoogle.el, and add the following to your .emacs:

(require 'hoogle)
(global-set-key (kbd "C-c h") 'hoogle-lookup)

For more information, check out the docstring of hoogle-lookup.

6.3 Using rectangular region commands

Emacs has a set of commands which operate on the region as if it were rectangular. This turns out to be extremely useful when dealing with whitespace sensitive languages.

C-x r o is "Open Rectangle". It will shift any text within the rectangle to the right side. Also see:

C-x r t is "String Rectangle". It will shift any text within the rectangle over to the right, and insert a given string prefixing all the lines in the region. If comment-region didn't already exist, you could use this instead, for example.

C-x r d is "Delete Rectangle". It will delete the contents of the rectangle and move anything on the right over.

C-x r r is "Copy Rectangle to Register". It will prompt you for a register number so it can save it for later.

C-x r g is "Insert register". This will insert the contents of the given register, overwriting whatever happens to be within the target rectangle. (So make room)

C-x r k is "Kill rectangle". Delete rectangle and save contents for:

C-x r y is "Yank rectangle". This will insert the contents of the last killed rectangle.

As with all Emacs modifier combos, you can type C-x r C-h to find out what keys are bound beginning with the C-x r prefix.

6.4 Aligning code

Emacs22 has a neat tool called: align-regexp. Select a region you want to align text within, M-x align-regexp, and type a regexp representing the alignment delimiter.

For example, I often line up my Haddock comments:

f :: a -- ^ does a
  -> Foo b -- ^ and b
  -> c -- ^ to c

Select the region, and let the regexp be: --

f :: a     -- ^ does a
  -> Foo b -- ^ and b
  -> c     -- ^ to c

Of course, this works for just about anything. Personally, I've globally bound it to C-x a r:

(global-set-key (kbd "C-x a r") 'align-regexp).