In diesem Kapitel werden ein paar Beispielsanwendungen gezeigt, welche sich mit Fay übersetzen lassen.
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
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.
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.
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.