Introduction to Functional Programming – Haste: Running Haskell in the BrowserTDA555 / DIT440, LP1, HT2013
Home | Schedule | Labs | Exercises | Exam | AboutFire | Forum | TimeEdit | Links | 2012
Links:
  • Installing Haste
  • Haste libraries
  • Haste repository
  • Introduction

    Haste is a compiler from Haskell to Javascript, which is a common language for programming interactive web pages. Using Haste it is possible to use Haskell to specify the behavior of web pages. But before we can talk about the interactive behavior of web pages, we need to know how to express the content of web pages.

    The main language for specifying the content of web pages is HTML. An example HTML document is the following:

    <!DOCTYPE html>
    <html>
      <body>
        <h1>My web page</h1>
    
        This document has a heading and some text. <em>This sentence is emphasized.</em>
      </body>
    </html>
    
    When loading this document in a browser, a page like the following will be displayed:

    My web page

    This document has a heading and some text. This sentence is emphasized.

    HTML documents are structured using tags, which usually come in pairs; e.g. <html> ... </html>, <h1> ... </h1>, etc. The content within a pair of tags is referred to as an element, and elements may be nested. For example, the above example contains an <body> element, which, in turn contains an <h1> element.

    Note that the browser does not display the HTML tags, but uses the tags to interpret the content of the page.

    The following tags are useful when creating simple web pages:

  • <html> ... </html> Specifies the root of the document. All other elements should be inside this tag.
  • <body> ... </body> Specifies the content of the document. All visible content should be inside this tag.
  • <h1> ... </h1> A level-1 heading (similarly <h2>, <h3>, etc. for lower-level headings).
  • <p> ... </p> A paragraph.
  • <em> ... </em> Emphasized text.
  • In addition, the <br/> tag specifies a newline. It is an unpaired tag that does not have a corresponding closing tag.

    Interactive web pages

    To create pages that react to user input one can make use of buttons (<button>) and text entries (<input>). Here is a simple example of a text entry with a button below:
    <input type="text" size="30" id="answer" value="Type your answer here..."></input>
    <br/>
    <button id="submit">Submit answer</button>
    
    This example will be displayed as follows in the browser:

    The purpose of the element identifiers (id="answer" and id="submit") is to make it possible to refer to these elements from scripting code. For example, a script may be set to wait for a click on the Submit button. Once a click is received, it may read the contents of the text entry, and perhaps update some other part of the page depending on the submitted answer.

    In order to add an identifier to a piece of text (e.g. so that it can be updated by a script), use a <span> tag:

    <span id="some-identifyer"> ... </span>
    

    To attach a script (typically Javascript) to a web page, use the <script> tag:

    <html>
      <head>
        <script src="script.js" type="text/javascript">...</script>
      </head>
      <body>
        ...
      </body>
    </html>
    
    This will use the Javascript program script.js to control the content of the page.

    In this course, we will not need to know anything about Javascript. We will simply use Haste to compile Haskell code to Javascript, and then use a <script> tag to attach this code to a web page. That's all.

    Scripting using Haste

    First make sure that you have installed Haste.

    Haste is capable of compiling arbitrary Haskell code to Javascript. However, in order to use Haste for scripting web pages, one has to make use of its built-in library:

    import Haste
    
    This library (and other Haste-libraries) is documented here.

    HelloWorld.hs is a simple example of a Haste script that opens a small dialogue window as soon as the page is loaded:

    main :: IO ()
    main = alert "Hello world!"
    
    To compile a Haskell file such as HelloWorld.hs into Javascript, run the following command in the terminal:
    > hastec HelloWorld.hs
    

    Connecting to the document

    To connect a script to elements in the document, use elemById, e.g. as such:
    Just inp  <- elemById "user-input"
    Just outp <- elemById "script-output"
    
    The above will retrieve the elements identified by "user-input" and "script-output" respectively. These variables can then be used, e.g. to read from the input element
    text <- getProp inp "value"
    
    and to write this text to an output element
    setProp outp "innerHTML" text
    
    The example Echo.hs demonstrates this kind of reading and writing. Here, the interactive code is put inside a "callback":
    onEvent inp OnKeyUp $ \_ -> do
        text <- getProp inp "value"
        setProp outp "innerHTML" text
    
    This will cause the do block to be re-run whenever the user releases a key in the text entry identified by inp.

    Calculator.hs is a similar example, but where the script actually computes a result, rather than just copying text.

    Graphics in the browser

    The HTML5 standard includes a tag <canvas> for drawing figures in the browser. For example, to include a 300x300 pixels drawing area in a document, simply include
    <canvas id="canvas" style="border: 1px solid black;" width="300" height="300"></canvas>
    
    somewhere in the body of the document.

    In order to interface with the canvas from the Haskell code, one first has to import Haste's canvas library

    import Haste.Graphics.Canvas
    
    To see the contents of this library, have a look at its source code.

    This library contains two monads used for drawing: Shape and Picture. Basic shapes can be created using the basic functions

    line   :: Point -> Point -> Shape ()
    rect   :: Point -> Point -> Shape ()
    circle :: Point -> Double -> Shape ()
    
    A Point is a pair of floating point numbers corresponding to the number of pixels in the x- and y-dimension, respectively:
    type Point = (Double,Double)
    

    Combining pictures

    Shapes can be combined using do-notation:
    snowMan :: Double -> Shape ()
    snowMan x = do
        circle (x,100) 20
        circle (x,65) 15
        circle (x,40) 10
    
    A Shape can be turned into a picture in two basic ways:
    fill   :: Shape () -> Picture ()
    stroke :: Shape () -> Picture ()
    
    The former function fills the shape with solid color, while the latter only draws the contours. Pictures are also combined using do-notation:
    twoSnowMenInABox :: Picture ()
    twoSnowMenInABox = do
        fill   $ snowMan 100
        stroke $ snowMan 200
        stroke $ rect (50,10) (250,150)
    
    This will result in the following picture:

    (The lines on the right-hand side of the snow men are probably due to a bug (reported).)

    To actually draw the above picture on the canvas, we can use the following main definition:

    main :: IO ()
    main = do
        Just can <- getCanvasById "canvas"
        render can twoSnowMenInABox
    
    See Snowmen.hs .

    Animations

    It is possible to animate pictures in a canvas simply by periodically updating the picture. FallingBall.hs is a simple animation of a falling ball. Here, the animation is achieved by the function fall recursively calling itself after a time-out of 20 milliseconds. The y argument of fall acts as the state of the animation; by multiplying y in the recursive call, a slightly different picture is drawn in each iteration.

    Finally BouncingBalls.hs is a more advanced animation, where the state is updated interactively by clicking in the canvas using the mouse. For this, an IO reference is used to hold the current state. Learn more about references here: Data.IORef.

    Troubleshooting

    If you get strange errors from Haste, e.g. complaining about missing files, you may want to check the following:
  • Your main module (the module that you pass to the hastec command) should be called Main. That is, it should start with the line
    module Main where
  • That you don't import modules from packages that are not installed in Haste. For example, QuickCheck might not be installed.