Fay

Haskell auf Client-Seite

Alexander Mills (inf9433@fh-wedel.de)

Startseite | Inhaltsverzeichnis | Einleitung | Funktionsweise | Beispiele | Einschränkungen | Fazit | Quellen

Programmbeispiele für Fay

In diesem Kapitel werden ein paar Beispielsanwendungen gezeigt, welche sich mit Fay übersetzen lassen.

Naive Fibonaccifunktion

Ein einfaches Programm, welches sich mit Fay kompilieren lässt, ist die naive (quadratische Laufzeit) Implementierung der Fibonaccifunktion.

  import Prelude
  
  fib :: Int -> Int
  fib 0 = 0
  fib 1 = 1
  fib n = fib (n - 1) + fib (n - 2)
  
  main = putStrLn (show $ fib 10)
  

Das Programm berechnet die zehnte Fibonacci Zahl und gibt diese dann aus. Es lässt sich mithilfe des GHC kompilieren und ausführen.

Mithilfe von Fay lässt sich dieses Haskellprogramm nun nach JavaScript kompilieren.

  Fay fib.hs
  

Fay erstellt eine JavaScript-Datei, welche mit nodejs ausgeführt oder in eine HTML Seite eingebunden werden kann. Beim Öffnen in einem Browser muss lediglich die Konsole geöffnet werden, da auf diese das Ergebnis ausgegeben wird.

  node fib.hs
  => 55
  


Online Fibonaccirechner

[Ausprobieren]

Ein fortgeschritteneres Beispiel wäre ein Fibonaccirechner, welcher über ein Webinterface bedienbar ist. Hierzu sind einige "ffi"-Funktionen nötig.

  module Fib(calc) where

  import FFI
  import Prelude
  
  --Ausgabefunktion
  alert :: String -> Fay()
  alert = ffi "alert(%1)"
  
  --Gibt den Wert eines Seitenelements zurück
  getElem :: String -> String
  getElem = ffi "document.getElementById(%1).value"

  --String zu Integer Umformung
  strToInt :: String -> Int
  strToInt = ffi "parseInt(%1)"  

  --"Vernünftige" Fibonaccifunktion
  fib :: Int -> Int
  fib n = fibs !! n
    where fibs = 0:1:(zipWith (+) fibs (tail fibs))
    
  --Startet die Berechnung
  --Holt die Zahl aus dem Textfeld, wandelt sie zu einem Int, berechnet die gewünschte Fibonaccizahl und gibt diese dann aus
  calc :: Fay()
  calc = alert $ show $ fib $ strToInt $ getElem "zahl"
  

Zum Kompilieren wird diesmal das Flag "--library" gesetzt, um nicht die (nicht definierte) Main-Funktion aufzurufen.

  fay fibHtml.hs --library
  

Die HTML-Seite für das Rechner sieht wie folgt aus:

  <html>
    <head>
      <title>Fibonacci Rechner</title>
      <script language="javascript" src="fib.js"></script>
      <style type="text/css">
      </style>
    </head>

    <body>
      <p>Fibonaccizahl:</p>
      <input type="text" id="zahl">
      <br>
      <br>
      <input type="button" value="Rechne" onclick="var f = new Fib(); f._(f.Fib$calc)">
    </body>
  </html>
  

Wird der Button gedrückt, so wird eine neue Instanz der Klasse "Fib" erzeugt, von der die Funktion "calc" ausgeführt wird. Es muss jedes mal eine neue Instanz erzeugt werden, da nach einem Aufruf von "calc" die Funktion ausgewertet ist und nicht erneut ausgeführt wird.

Auch wenn dieser Fibonaccirechner wesentlich effizienter ist als mit der naiven Implementierung, so stellt sich bei Eingabe großer Werte (ab zirka 100) heraus, dass JavaScript die Fibonaccizahl nur auf die erste 15-16 Stellen berechnet. Dies liegt an der Genauigkeit von Doubles, da JavaScript ALLE Zahlen intern als Double speichert.


Datentyp "Hack"

Dieses kleine Beispiel soll zeigen, dass Fay nicht das richtige Verhalten von Datentypen sicherstellt. Dies ist besonders bei der Verwendung von "ffi"-Funktionen relevant, da deren Inhalt nicht überprüft wird.

  hack :: Double -> Int
  hack = ffi "%1"
  
  main = putStrLn $ show (2 * hack 1.4)
  

Fay übersetzt die Funktion "hack", indem der Parameter einfach von "fayToJs" nach "jsToFay" weitergereicht wird. Die Konvertierungsfunktionen für Double und Int geben wie bereits erwähnt nur den ausgewerteten Parameter unverändert zurück.

Führt man dieses Programm mit nodejs aus, so ist die Ausgabe "2.8", was definitiv kein Int ist.



Eingabeüberprüfung mit Nebeneffekten

[Ausprobieren]

Das folgende Programm soll einige Felder eines Eingabeformulars auf Konsistenz überprüfen. Ist ein Pflichtfeld leer oder ungültig gefüllt, soll eine Fehlermeldung ausgegeben werden. Zu den Pflichtfeldern gehören Vor- und Nachname (dürfen nicht leer sein), eine Altersangabe (darf nicht leer sein und muss eine gültige Zahl kleiner als 120 sein) und mehrere Checkboxen, von denen mindestens eine angewählt sein muss.

Die Felder befinden sich in einem Formular, welches per GET-Methode die Daten an eine andere HTML-Seite weitergeben soll, wenn die Prüfung erfolgreich ist.

Funktionsaufruf in HTML:

  <form name="formular" id="formular" action="rueckmeldung.html" method="get" onsubmit="var x = new Pruef(); return x._(x.Pruef$pruef)">
  

Haskellcode:

  module Pruef(pruef, strIsNumber, strToInt) where

  import Prelude
  import FFI

  alert :: String -> Bool
  alert = ffi "alert(%1)"

  strToInt :: String -> Int
  strToInt = ffi "parseInt(%1)"

  strIsNumber :: String -> Bool
  strIsNumber s = ((show (strToInt s)) == s)

  getTextField :: String -> String
  getTextField = ffi "document.getElementById(%1).value"

  getCheckBox :: String -> Bool
  getCheckBox = ffi "document.getElementById(%1).checked"

  getAge :: String
  getAge = getTextField "alter"

  validAge :: String -> Bool
  validAge s = (not (null s)) && (strIsNumber s) && (head s /= '0') && ((strToInt s) < 120)

  testHobbies :: Bool
  testHobbies = getCheckBox "hobby1"
              || getCheckBox "hobby2"
              || getCheckBox "hobby3"
              || getCheckBox "hobby4"
            
  testNames :: Bool
  testNames = not (null (getTextField "vorname") 
              || null (getTextField "nachname"))
            
  testAge :: Bool
  testAge = validAge $ getTextField "alter"

  pruefErr :: Bool -> Bool -> Bool -> Bool
  pruefErr False _ _ = alert "Namensangabe unvollstaendig!"
  pruefErr _ False _ = alert "Ungueltige Altersangabe!"
  pruefErr _ _ False = alert "Kein Hobby angegeben!"


  pruef :: Bool
  pruef = ((pruefErr testNames testAge testHobbies) || True) && testNames && testAge && testHobbies 
  

Um sowohl eine Fehlermeldung auszugeben als auch einen Rückgabewert vom Typ Boolean zu haben, wurde bei diesem Programm für den Rückgabewert einer "ffi"-Funktion ein eigentlich ungültiger Wert angenommen. Im JavaScritpcode wird der Rückgabewert von alert ("undefined") als Boolean interpretiert und zurückgegeben. Die Funktion "pruef" verwendet dann diese Funktion zusammen mit "|| True" um das "undefined" zu umgehen und den Rest des Programms auszuführen.