2012-09-05 5 views
7

Ich habe einige Codes, die Struktur entspricht dies hat:Wie stelle ich die Effizienz bei Teilanwendungen in reinem Haskell sicher?

import Debug.Trace 

newtype SomeExpensiveHiddenType = SCHT Double 

expensive :: Double -> Double -> SomeExpensiveHiddenType 
expensive a b = SCHT $ trace "call expensive" (*) a b 

cheap :: SomeExpensiveHiddenType -> Double -> Double 
cheap (SCHT x) c = trace "call cheap" (+) x c 

f1 :: Double -> Double -> Double -> Double 
f1 a b c = let x = expensive a b in cheap x c 

d.h. f1 ist eine Funktion, die ein teures Ergebnis auf der Grundlage der ersten beiden Argumente berechnet, und verwendet dann diese mit dem dritten Argumente. Ich hatte gehofft, dass eine teilweise Anwendung auf die ersten beiden Argumente und die wiederholte Anwendung des dritten Arguments dazu führen würden, dass die teure Berechnung nur einmal ausgeführt würde. Leider ist dies nicht der Fall:

test1 = do 
    putStrLn "test 1" 
    let p = f1 2 3 
    print (p 0.1) 
    print (p 0.2) 
    print (p 0.3) 

Ergebnisse in:

*Main> test1 
test 1 
call cheap 
call expensive 
6.1 
call cheap 
call expensive 
6.2 
call cheap 
call expensive 
6.3 
*Main> 

ich habe kommen mit, was scheint eine Lösung zu sein:

newtype X a = X { unX :: a } 
f2 :: Double -> Double -> X (Double -> Double) 
f2 a b = let x = expensive a b in X (cheap x) 

test2 = do 
    putStrLn "test 2" 
    let p = unX $ f2 2 3 
    print (p 0.1) 
    print (p 0.2) 
    print (p 0.3) 

in resultierenden:

*Main> test2 
test 2 
call cheap 
call expensive 
6.1 
call cheap 
6.2 
call cheap 
6.3 
*Main> 

Aber das scheint ziemlich chaotisch . Gibt es eine sauberere Möglichkeit, die redundanten Aufrufe an die teure Kalkulation zu vermeiden?

+3

Testen Sie auch solche Dinge nicht in ghci, kompilieren Sie mit Optimierungen. In einfachen Fällen wie diesem teilt GHC dann 'p' auch mit der ursprünglichen Definition von' f1'. (Aber Sie sollten den Rat von dbaupp trotzdem verwenden, in komplizierteren Situationen kann der Compiler das Teilen nicht selbst einführen.) –

+0

Ich habe hier bewusst ghci verwendet, da es sich auf die Optimierung als gefährlich erweist. Die Laufzeit des realen Programms unterscheidet sich um eine Größenordnung, wenn die Freigabe nicht erkannt wird. – timbod

+0

Richtig, Sie sollten sich (blind) nicht auf Optimierungen verlassen. Deshalb habe ich gesagt, du solltest dem Rat von Dbaupp folgen. Wenn Sie jedoch wissen möchten, wie sich Ihr Code verhält, können Sie Ihren Code nicht wirklich testen. Der Code, den Sie in ghci testen, ist anders, er kann völlig andere Eigenschaften haben als der echte Code. GHCi eignet sich hervorragend zum Testen der Korrektheit, aber weder für die Geschwindigkeit noch für die Speichernutzung. –

Antwort

9

Sie können einfach das dritte Argument innerhalb der let setzen, so dass x freigegeben ist.

f2 a b = let x = expensive a b in \c -> cheap x c 

(In diesem Fall f2 a b = let x = expensive a b in cheap x auch funktioniert.)


Was Sie suchen ist-Compiler angetrieben partial evaluation, und das ist ein schwieriges Problem ... zumindest ist es ausreichend hart ist richtig implementieren, dass es nicht in GHC ist.

+0

(Das war schnell!) Ich bestätige, dass das tatsächlich mein Problem löst. Bis jetzt hätte ich meinen f1 und deinen f2 als gleichwertig betrachtet. Ich werde diese Transformation in Zukunft berücksichtigen müssen, wenn ich Funktionen schreibe, die teilweise angewendet werden sollen. – timbod

+0

@timbod Ihr 'f1' ist' \ a -> \ b -> \ c-> Lassen Sie x = expns ab in billigen xc', und das 'f2' ist hier' \ a -> \ b-> lassen x = kostet ab in c-> billiges xc'. Was durch * eta-Reduktion * ist '\ a -> \ b-> lex x = expens a b in billiges x' (auch hier). Das ist genau gleichbedeutend mit * your 'f2' *, :) Es ist nicht einmal ein Boxing involviert, da' newtype' zur Kompilierzeit eliminiert wird. 'newtype' wird normalerweise verwendet, um eine alternative 'instance'-Implementierung einer Typklasse oder einer Art Trickery zu ermöglichen. Um * float * das * let * 'x = expens a b' über' \ c-> '* lambda * ist ein mutiger Zug, schwer 4 _ein Compiler_ zu entscheiden, ob es das wert ist oder nicht. –

Verwandte Themen