Das Problem
Ich möchte in Haskell Funktionen simulieren, die mehrere Werte ausgeben. Der Haskell-Code wird generiert (nicht von Hand geschrieben) - dies sind wichtige Informationen, siehe unten:
Dies kann natürlich einfach durch Rückgabe eines Tupels aus der Funktion erfolgen, wie zum Beispiel
f x y = (x+y, x-y)
Aber beim Verwenden einer solchen Funktion muss ich wissen, was für ein Tupel es zurückgibt:
...
(out_f_1, out_f_2) = f a b
(out_g_1, out_g_2, out_g_3) = g out_f_1
...
Und so weiter ... Aber während des Code-Generierens weiß ich nicht, was für ein Typ der Ausgang von z.B. f ist, daher verwende ich derzeit das Paket Data.List.Select
und simuliere das Obige mit:
import Data.List.Select
...
out_f = f a b
out_g = g (sel1 outf)
...
Das Problem ist die Leistung - in meinem Testprogramm ist die Version, die Data.List.Select
verwendet, doppelt so langsam wie die von Hand geschriebene Version.
Dies ist eine sehr offensichtliche Situation, da Data.List.Select
unter Verwendung von Klassen
und Instanzen
geschrieben ist, so dass es so etwas wie ein Laufzeitwörterbuch verwendet (wenn ich mich nicht irre). (http://hackage.haskell.org/packages/archive/tuple/0.2.0.1/doc/html/src/Data-Tuple-Select.html#sel1)
Die Frage
Ich möchte fragen, ob es möglich ist, die Version, die Data.List.Select
verwendet, irgendwie so zu kompilieren, dass sie so schnell ist wie die manuell erstellte?
Ich denke, es sollte einen Schalter für den Compiler geben, der ihm sagt, die Klassen und Schnittstellen für jede Verwendung zu "instanziieren" (etwas Ähnliches wie Templates aus C++).
Benchmarks
Test1.hs:
import qualified Data.Vector as V
import System.Environment
b :: Int -> Int
b x = x + 5
c x = b x + 1
d x = b x - 1
a x = c x + d x
main = do
putStrLn "Starting..."
args <- getArgs
let iternum = read (head args) :: Int in do
putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> a (iternum-i))
$ V.enumFromTo 1 iternum
putStrLn "Done."
Kompilieren mit ghc -O3 Test1.hs
Test2.hs:
import qualified Data.Vector as V
import Data.Tuple.Select
import Data.Tuple.OneTuple
import System.Environment
b x = OneTuple $ x + 5
c x = OneTuple $ (sel1 $ b x) + 1
d x = OneTuple $ (sel1 $ b x) - 1
a x = OneTuple $ (sel1 $ c x) + (sel1 $ d x)
main = do
putStrLn "Starting..."
args <- getArgs
let iternum = read (head args) :: Int in do
putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> sel1 $ a (iternum-i))
$ V.enumFromTo 1 iternum
putStrLn "Done."
Kompilieren mit ghc -O3 Test2.hs
Ergebnisse
time ./Test1 10000000 = 5.54 s
time ./Test2 10000000 = 10.06 s