Schrift
[thread]12922[/thread]

Funktionale Programmierung in Perl

Leser: 2


<< |< 1 2 >| >> 11 Einträge, 2 Seiten
neniro
 2008-12-19 23:29
#117364 #117364
User since
2008-12-14
79 Artikel
BenutzerIn
[default_avatar]
Hallo zusammen,

Lan-X hatte mich gebeten folgendes Beispiel auch mal im Forum zur Diskussion zu stellen. Beim letzten darmstadt.pm habe ich erwähnt, dass ich mir ein Buch zu F# gekauft habe. Die meisten Beispiele sind problemlos nach Perl portierbar. Wer das HOP kennt, gewinnt nochmal 'ne etwas andere Perspektive. Anbei mal ein Beispiel für eine Funktion ($d->($f) bildet die Ableitung) die sowohl direkt, als auch mit Curry ;-) genossen werden kann:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my $EPSILON = 1e-7;

my $f = sub {
  my $x = shift;
  return $x ** 2 - 2 * $x + 7;
};

my $d = sub {
  my ($f, $x) = @_;
    my $d_ = sub {
      my $x = shift;
      return sprintf "%.3f",
          ($f->($x+$EPSILON) - $f->($x-$EPSILON))/( 2 * $EPSILON );
  };
    return defined $x ? $d_->($x) : $d_;
};

print Dumper [ map $f->($_), 0..9 ];
print Dumper [ map $d->($f, $_), 0..9 ];

my $d_of_f = $d->($f);
print Dumper [ map $d_of_f->($_), 0..9 ];

Natürlich können Sprachen wie Haskell oder F# das noch etwas eleganter, aber die Möglichkeit eine Funktion direkt mit einem Parameter auszuwerten und ansonsten eine Funktion zurückzugeben, die man später mit konkreten Werten nutzen kann, ist schon schick.

Wenn es euch interessiert, liefere ich gerne noch ein paar interessante Beispiele wenn ich in dem Buch vorankomme.
-- yet another amateur perl hacker
LanX-
 2008-12-19 23:50
#117365 #117365
User since
2008-07-15
1000 Artikel
BenutzerIn

user image
lieber "neniro", vielleicht solltest du mir dein passwort mailen, damit du nicht alle naslang ein neuen nick brauchst wenn deine Cookies verloren gehen... *fg*


UPDATE: ... oder mails dir selbst! Beeindruckend, 2022 Beiträge verlieren jetzt ihren Papa ...
LanX-
 2008-12-20 00:07
#117366 #117366
User since
2008-07-15
1000 Artikel
BenutzerIn

user image
Mal ein bischen Erläuterung:

Also rein mathematisch wird hier punktweise die Steigung angenähert, was ja genau die Definition für die Ableitung ist, wenn Epsilon gegen 0 geht.*

Das besondere am sub d() ( d.h. Differential, man denke an die d f / d x Notation) ist hier, dass es wahlweise die Ableitung an einer Stelle x liefert, also f'(x) oder halt nur die Funtion f' wenn kein x angegeben ist. Ihr seht das ist syntaktisch ziemlich analog zur mathematischen Notation.

Die beiden letzten maps machen deswegen genau das gleiche, nämlich die Ableitung an 10 Punkten berechnen.


* noch genauer wärs wenn man den Mittelwert der beiden Steigungen von f(x) zu f(x-e) und f(x+e) nähme statt nur f(x-e) zu f(x+e) ... mit e=Epsilon!
murphy
 2008-12-20 03:30
#117369 #117369
User since
2004-07-19
1776 Artikel
HausmeisterIn
[Homepage]
user image
Anmerkungen:

- Da die Differentiation eine Operation ist, die, bildlich gesprochen, den Funktionsverlauf "aufraut" und auch kleine Unregelmaessigkeiten stark verstaerken kann, ist es meiner Meinung nach eine ziemlich bloede Idee, eine generelle numerische Differentiationsroutine fuer beliebige Funktionen zu schreiben, die keinerlei adaptive Schrittweitensteuerung, am besten mit Fehlerabschaetzung, verwendet, wenn einem die numerische Stabilitaet des Resultates nicht voellig egal ist. Auch wenn man die Steigung am Mittelpunkt eines Intervalles berechnet, wie LanX- es vorschlaegt, ist das noch keine Loesung fuer das Stabilitaetsproblem, auch wenn die Naeherung dadurch in der Tat besser wird. Und das mindeste ist, dass man bei einer numerischen Berechnung die Fehlergrenzen mit zurueckliefert oder bei Ueberschreitung bestimmter Grenzen warnt oder einen Fehler erzeugt.

- Wenn man eine Subroutine schreibt, die eine Codereferenz als Parameter nimmt, ist der Prototyp & oft praktisch und spart Schreibarbeit.

- Currying ist zwar bisweilen eine praktische Sache, ich schrecke aber meist davor zurueck, es gleich in eine Funktion mit einzubauen, weil mir die verlorene Fehlerueberpruefungsmoeglichkeit bei der Parameterueberhabe nicht behagt (Ist es gewolltes Currying, oder hat hier jemand vergessen, einen Parameter zu uebergeben? Sollte die Funktion wirklich komplett evaluiert werden, oder steht da ein Parameter zu viel?). Gerade bei einer schwach typisierten Sprache passt mir das nicht -- aber andererseits hat Perl sowieso keine eingebaute Ueberpruefung der Argumentlistenlaengen, solange man keine Prototypen benutzt ;-)
When C++ is your hammer, every problem looks like your thumb.
murphy
 2008-12-20 03:43
#117370 #117370
User since
2004-07-19
1776 Artikel
HausmeisterIn
[Homepage]
user image
Nachtrag:
LanX-+2008-12-19 23:07:32--
[...] Also rein mathematisch wird hier punktweise die Steigung angenähert, was ja genau die Definition für die Ableitung ist, wenn Epsilon gegen 0 geht. [...] noch genauer wärs wenn man den Mittelwert der beiden Steigungen von f(x) zu f(x-e) und f(x+e) nähme statt nur f(x-e) zu f(x+e) ... mit e=Epsilon!


So wie Du das sagst, klingt es irgendwie komisch: Wenn e=Epsilon ist, warum benutzt Du dann ueberhaupt zwei verschiedene Variablen? Und im Grenzwert Epsilon gegen Null macht es auch keinen Unterschied, ob ich die linksseitige, die rechtsseitige oder die mittlere Ableitung an einer Stelle berechne, es sei denn, die Funktion ist im strengen Sinne dort gar nicht differenzierbar.

Solange Epsilon endlich ist, ist die mittlere Ableitung aber wirklich die bessere Approximation. Ausser eben an Stellen, wo einen eigentlich nicht die Ableitung, sondern der links- oder rechtsseitige Grenzwert derselben zu dieser Stelle hin interessiert, weil die Funktion dort nicht differenzierbar ist.

Das numerische Differenzieren ist eben fast komplizierter korrekt durchzufuehren als das numerische Integrieren, obwohl es theoretisch die einfachere Operation ist -- irgendwie paradox, nicht wahr?
When C++ is your hammer, every problem looks like your thumb.
LanX-
 2008-12-20 04:43
#117371 #117371
User since
2008-07-15
1000 Artikel
BenutzerIn

user image
@Murphy: Also wir sollten das Beispiel nicht überstrapazieren, ich hab nur die Interpretation des Codes geliefert, das es klügere nummerische oder algebraische Verfahren gibt brauchen wir nicht zu diskutieren, es ist eben nur ein Beispiel für funktionales Proggen.


Und e=epsilon hab ich geschrieben weil ich kein Epsilon auf meiner Tastatur gefunden habe ...

Auf Analysis hab ich eh wenig Bock ...



UPDATE: Und ich hab gleich gesagt dass es sich hier nur um eine Näherung handelt. So viel anders funktionieren nummerische Verfahren nämlich auch nicht, sie verfeinern nur eben diesen Ansatz mit Diffrenzenquotienten
neniro
 2008-12-23 12:11
#117402 #117402
User since
2008-12-14
79 Artikel
BenutzerIn
[default_avatar]
Rekursion ist eines der wesentlichen Werkzeuge bei funktionaler Programmierung. Vielen Programmierern bereitet Rekursion erstmal (und dann immer mal wieder) Kopfschmerzen. Man kann natürlich die entsprechende Funktion mit Debugging-Code vollstopfen um zu sehen, was da vor sich geht, oder man wrappt den Debugging-Code um die Funktion herum (in der OO-Welt als Decorator-Pattern). Hier ein Beispiel mit der Funktion g() die Rekursiv die Fakultät einer Zahl ermittelt:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;
use Sub::Identify;
use Sub::Install;

sub g {
    my $i = shift;
    return 1 if $i == 0;
    return $i * g->($i-1);
}

# Decorator

sub trace_sub {
    my $sub     = shift;
    my $subname = Sub::Identify::sub_name( $sub );
    my $lvl     = 0;
    
    Sub::Install::reinstall_sub({
        code => sub {
            my @args = @_;
            print "  " x $lvl++ ."called $subname(" . join(', ', @args) .")\n" ;
            my $value = $sub->(@args);
            print "  " x --$lvl ."$subname(". join(', ', @args) .") returns $value\n"; 
            return $value;
        },
        as   => $subname,
    });
    
    return;
}

trace_sub(\&g);
print g(4);

Der Wrapper nutzt zusätzliche Ausgaben per print und Einrückung um den Verlauf der Rekursion darzustellen:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
called g(4)
called g(3)
called g(2)
called g(1)
called g(0)
g(0) returns 1
g(1) returns 1
g(2) returns 2
g(3) returns 6
g(4) returns 24
24

Natürlich könnte man das Ganze noch etwas spektakulärer gestalten, z.B. mit einer rekursiv definierten Fibonacci-Funktion und der Visualiiserung über Graphviz, aber das Grundprinzip wird klarer.
-- yet another amateur perl hacker
murphy
 2008-12-23 16:25
#117415 #117415
User since
2004-07-19
1776 Artikel
HausmeisterIn
[Homepage]
user image
Anmerkung: In der funktionalen Welt werden oft auch Dinge, die ein Perlprogrammierer eher mit for oder while loesen wuerde, als rekursive Funktionen geschrieben. Kommt man in Perl mal in Versuchung, auch so etwas zu machen, zum Beispiel weil man keine ganz einfache Schleife hat, sondern zwei oder mehr Subroutinen, die sich gegenseitig aufrufen, dann sollte man beachten, dass bei tiefer Rekursion schon mal der Stack ueberlaufen kann oder Perl zumindest eine Warnung ausspuckt. Zum Glueck kann man aber viele rekursive Funktionen so schreiben, dass der rekursive Aufruf direkt mit dem return-Statement gekoppelt ist (sub foo { ...; return foo(...); }). In so einem Fall ist das anlegen zusaetzlicher Stackframes fuer den Aufruf eigentlich unnuoetig und manche Programmiersprachen optimieren so einen Aufruf direkt zu einer Schleife im Maschinencode, aber in Perl kann man sich mit folgendem Konstrukt behelfen: sub foo { ...; @_ = ...; goto &foo; }. Macht man das so, wird der Stack dadurch nicht weiter gefuellt.

Uebungsaufgabe: Die Fakultaetsfunktion aus neniros Beitrag ist nicht in dieser "endrekursiven" Form geschrieben, da der Aufruf zu g innerhalb eines anderen Ausdrucks erfolgt. Es ist aber moeglich, sie in die endrekursive Form umzuschreiben...
When C++ is your hammer, every problem looks like your thumb.
neniro
 2008-12-26 19:31
#117450 #117450
User since
2008-12-14
79 Artikel
BenutzerIn
[default_avatar]
Eine der Sachen, für die ich (aktuell) keine Entsprechung in Perl5 (für P6 kann ich noch nix sagen) kenne, ist das, was als "PatternMatching" bezeichnet wird. Erst dachte ich, dass man es problemlos mit regulären Ausdrücken abbilden könnte. Der Unterschied ist aber anscheinend, dass das "PatternMatching" direkt auch auf Funktionen angewandt werden kann, z.B. für refactoring o.ä. - um mal ein einfaches Beispiel zu beschreiben, könnte man eine expand-Funktion definieren, die per "PatternMatching" aus multiply(5, add(3, 2)) die ausführlichere Variante add(multiply(5,3), multiply(5,2)) erzeugt, also eine symbolische Umformung.
-- yet another amateur perl hacker
LanX-
 2008-12-26 21:00
#117452 #117452
User since
2008-07-15
1000 Artikel
BenutzerIn

user image
neniro+2008-12-26 18:31:19--
die per "PatternMatching" aus multiply(5, add(3, 2)) die ausführlichere Variante add(multiply(5,3), multiply(5,2)) erzeugt, also eine symbolische Umformung.


Also in Perl ne Art Codefilter, aber zuverlässig? Sieht für mich wie eine typische Anwendung von Macros aus, die es ja so in Perl leider nicht gibt.

Ich kenn mich ja nur theoretisch mit Lisp aus, und mit dem strengen Listenyntax kann ich mirs irgendwo vorstellen ... aber geht sowas auch in Haskell???
<< |< 1 2 >| >> 11 Einträge, 2 Seiten



View all threads created 2008-12-19 23:29.