Haskell
-
@volkard: Du bestätigst meine Aussage. Du gibst ebenso nur unqualifizierten Müll ab, und du kannst nicht mal das Gegenteil rechtfertigen, oder willst du mir sagen, dass du Haskell mal ernsthaft unter die Lupe genommen und verwendet (!) hast, statt nur darüber zu lesen? Alle Punkte deines ersten Absatzes kann ich 1:1 zurückwerfen, indem ich "Haskell" und "C++" in deinem Beitrag vertausche. Ja, auch deinen inhaltlosen Spruch über "Haskell-Jünger". Unterschied zwischen uns beiden: Ich hätte Recht. Ich kenne beide Sprachen. Du nicht.
Zu deinem zweiten Absatz:
Vorteil 1: Hast du nur irgendwelche Benchmarks von 2005 oder auf Drogen geschriebene Blogs gelesen oder woher hast du den Blödsinn? Wenn du dir mal das Language-Shootout anschaust, wirst du feststellen, dass Haskell sich im selben Performance-Level bewegt wie C++. Oftmals ist es 20% langsamer, aber es ist auch gerne mal schneller. Außerdem hat mein Programm schon lange fertiggerechnet, während du noch dein C++-Programm schreibst. Die Parallelisierung als Kompensierung für schlechte Performance hinzustellen zeigt, dass dir die Argumente ausgehen, und belegen kannst du dein Geschwafel auch nicht.Vorteil 2: Auf den Quatsch gehe ich erst gar nicht ein, da du Haskell nicht verstehst. Wenn ich mich irre, magst du deine Aussage vielleicht mit einem Beispiel untermauern und nicht nur irgendwelche Sprüche in den Raum werfen? Schon witzig, dass hier so viele negative Aussagen über Haskell gemacht werden, aber keine davon wird mit aktuellen (!) Referenzen oder Beispielen belegt.
Vorteil 3: Ist dir eigentlich bewusst, dass C++0x/C++1x (oder wie auch immer es jetzt heißt) und C# viele Konzepte beinahe 1:1 von Haskell kopiert haben? Das Spiel geht noch weiter: F# hat das komplette Monadenkonzept in schwächerer Variante von Haskell übernommen, sogar inklusive einer abgeänderten Form der Syntax. Haskell ist also so schlecht, dass alle anderen Sprachen die Features übernehmen. Sogar die allgemeine Sprachsyntax, die ursprünglich von OCaml kommt, wurde in neueren Versionen an Haskell angelehnt. Und im Falle von C++ werden Sprachfeatures übernommen, speziell um die Schwächen vom C++-Standard auszumerzen. Informiere dich erst mal, bevor du dein Gebrabbel abgibst.
Falls du es noch nicht gemerkt hast: Ich interessiere mich allgemein für Programmiersprachen und nehme die Dinge genau unter die Lupe, bevor ich urteile. Also komm mir nicht mit deiner einseitigen C++-Scheuklappen-Ansicht.
CStoll schrieb:
Und wie unterscheidet sich der Typ der Identitätsfunktion von jeder anderen Umwandlungsfunktion in einem Typ (Nachfolger, Inversion, ...)?
Ich verstehe die Frage nicht, aber es gibt in Haskell genau drei Funktionen des Typs a → a. Die erste ist die Identitätsfunktion, die zweite geht mit ihrem Parameter in eine Endlosschleife (sie divergiert), die dritte ist selbst eine Endlosschleife. Unterschied zwischen den letzten beiden: Im ersteren Fall ist f noch definit und kann an eine strikte Funktion übergeben werden, ohne dass diese divergiert, f x nicht. Im letzteren Fall ist bereits f indefinit und führt eine strikte Funktion zur Divergenz. Du kannst keine andere Funktion schreiben, die diesen Typ besitzt.
Beim Ableiten ist die Frage: Welche vollständig definite (also nirgends divergierende) Funktion erfüllt diesen Typ? Mit anderen Worten: Welche Funktion, die niemals in eine Endlosschleife geht, nie eine Exception wirft, also generell immer zu einem Ergebnis konvergiert, erfüllt den angegebenen Typ? Ein abgeleitetes Programm terminiert also immer oder nutzt Korekursion, um mit der realen Welt zu kommunizieren (wenn man es wirklich so weit treiben will – ich nicht).
Ich gehe jetzt mal nicht komplett ins Detail, aber mit C++ kann ich genauso strenge Typprüfungen zulassen, indem ich die meine Klassen und Funktionen entsprechend restriktiv auslege.
Und solange du mir nicht zeigst, wie die Umsetzung dieser Funktionen aussehen würde, beweist dein Beispiel gar nichts.In Haskell musst du den Compiler umschreiben, um die Spezifikation zu brechen, in C++ nicht. Und in Haskell sind die Typen von Grund auf strikter, also schon in der base-Bibliothek. Das sind die Unterschiede, aber egal, lassen wir das Thema. Wie gesagt: Für mich ist das nur ein zusätzlicher Vorteil von Haskell, aber nicht der Hauptvorteil.
Für ein praktisches Beispiel kannst du dir gerne einige der Bibliotheken ansehen, die ich veröffentlicht habe. Drei Beispiele sind ismtp, ihttp und dnscache. Diese habe ich im produktiven Einsatz.
Sowiet ich die Diskussion bisher verfolgen konnte, was "das mit der Korrektheit" eines der Hauptargumente der Haskell-Befürworderer hier.
Mag sein, aber nicht meins.
Ich kann durchaus akzeptieren, daß C++ nicht unfehlbar ist. Aber du scheinst das von Haskell zu glauben.
Was die Mächtigkeit einer Sprache angeht, solltest du dich mal mit den Grundlagen der Berechenbarkeitstheorie beschäftigen.
Nein, ich glaube nicht, dass Haskell unfehlbar ist. Da gibt es bessere Sprachen, und ich habe auch welche genannt. Wenn es mir um Unfehlbarkeit ginge, würde ich in Agda programmieren, nicht in Haskell. Mir geht es aber eher um Produktivität. Mit den Grundlagen der Berechenbarkeitstheorie bin ich durchaus vertraut. Aber Mächtigkeit einer Programmiersprache basiert auf etwas anderem. Sonst müsste man schließen, dass es keinen Unterschied macht, ob man in C++ oder Assembler programmiert.
@knivil: Dann versuch dich mal an einem interaktiven System: Der Nutzer bekommt eine Sammlung von Datensätzen, kann davon einzelne suchen, bearbeiten und ändern. (z.B. ein Telefonbuch)
Ja, das ist eine sehr gute Anwendung. Mach eine Kommandozeilenversion daraus. Wenn diese funktioniert, ist es relativ leicht, daraus eine konkurrente Netzwerkversion zu machen, z.B. mit Sockets und STM. Hier zeigt Haskell auch seine wahren Stärken.
-
ertes schrieb:
CStoll schrieb:
Und wie unterscheidet sich der Typ der Identitätsfunktion von jeder anderen Umwandlungsfunktion in einem Typ (Nachfolger, Inversion, ...)?
Ich verstehe die Frage nicht, aber es gibt in Haskell genau drei Funktionen des Typs a → a. Die erste ist die Identitätsfunktion, die zweite geht mit ihrem Parameter in eine Endlosschleife (sie divergiert), die dritte ist selbst eine Endlosschleife. Unterschied zwischen den letzten beiden: Im ersteren Fall ist f noch definit und kann an eine strikte Funktion übergeben werden, ohne dass diese divergiert, f x nicht. Im letzteren Fall ist bereits f indefinit und führt eine strikte Funktion zur Divergenz. Du kannst keine andere Funktion schreiben, die diesen Typ besitzt.
OK, ich formuliere meine Frage neu: Wie würdest du in Haskell den Typ einer Nachfolger-Funktion oder der Negativ-Funktion (f(x)=-x) beschreiben?
Und nur zur Sicherheit: Was verstehst du unter dem Typ einer Funktion? Ich verstehe daunter die Angabe, welche Argument- und Ergebnistypen dieser Funktion (bzw. ihr Definitions- und Wertebereich).
-
Ihr kommt als Haskell-Programmierer ins C++ Land und erwartet, daß hier jeder eure Sprache versteht.
Noe, wir sind aus dem Land C++ nach Haskell-Heaven gegangen und berichten euch von unseren Erfahrungen. Es hat mich auch zu einem besseren C++ Programmierer gemacht. Natuerlich ist Ablehnung vorprogrammiert, wie z.B. volkard deutlich zeigt. Die Basis dieser Haltung ist aber eher religioeser Natur als wirklich fundiert. Schade ...
Ich verstehe die Frage nicht, aber es gibt in Haskell genau drei Funktionen des Typs a → a.
Es gibt genau eine, die fuer alle Typen sinnvoll ist, nicht nur im Kontext von Zahlen oder Listen. Betont man das nicht, fuehrt es oft zu Missverstaendnissen. Hier: http://www.youtube.com/watch?v=h0OkptwfX4g . Die Funktion f(x) = -x hat den Typ f :: (Num a) => a -> a . Sie macht also nur Sinn fuer Typen, die Instanzen von Num sind. Und bevor Fragen kommen: http://en.wikipedia.org/wiki/Type_class , aehnelt den Interfaces beispielsweise in Java.
@knivil: Dann versuch dich mal an einem interaktiven System: Der Nutzer bekommt eine Sammlung von Datensätzen, kann davon einzelne suchen, bearbeiten und ändern. (z.B. ein Telefonbuch)
Eine Liste von Haskellanwendungen findest du sicher im Web. Beispielsweise ist das Spiel DefCon von Introversion Software mit Haskell programmiert. Andere setzen Haskell im embedded Bereich ein.
-
CStoll schrieb:
OK, ich formuliere meine Frage neu: Wie würdest du in Haskell den Typ einer Nachfolger-Funktion oder der Negativ-Funktion (f(x)=-x) beschreiben?
Kommt darauf an, was du beweisen willst. Ohne Beweise sind die Typen ziemlich simpel:
negate :: Num a ⇒ a → a
succ :: Num a ⇒ a → aDas sind auch die Typen, die man in der base-Bibliothek findet. Wenn du hingegen Beweisführung haben willst, gibt es zwei Varianten. Beispiel: Du willst eine Funktion schreiben, die eine nichtnegative Zahl entgegennimmt. Der Typ könnte so aussehen:
sqrt :: PosInt → PosInt
Mit diesem Typ müsstest du allerdings zwei Negierungsfunktionen schreiben, was ja nicht Sinn der Sache ist. Eine wirklich sichere Alternative verwendet einen sog. Phantom-Typen:
data NegSign
data NonnegSign
data FlippedSign sdata SInt s where
SZero :: SInt NonnegSign
SSucc :: SInt NonnegSign → SInt NonnegSign
SNegate :: SInt s → SInt (FlippedSign s)class HasSign a where
type SignOf ainstance HasSign (SInt NonnegSign) where
type SignOf (SInt NonnegSign) = NonnegSigninstance HasSign (SInt NegSign) where
type SignOf (SInt NegSign) = NegSigninstance HasSign (SInt (FlippedSign NonnegSign)) where
type SignOf (SInt (FlippedSign NonnegSign)) = NegSigninstance HasSign (SInt (FlippedSign (FlippedSign s))) where
type SignOf (SInt (FlippedSign (FlippedSign s))) = SignOf ssqrt :: (HasSign a, SignOf a ~ NonnegSign) ⇒ a → a
Wie du siehst, ist das schon sehr viel Tipparbeit, um statisch zu garantieren, dass sqrt nur nichtnegative Zahlen erhält. Abgesehen davon ist die Zahlendarstellung, die ich oben gewählt habe, sehr ineffizient. Daran ließe sich etwas machen, aber dann wäre der Code noch länger. Haskell ist für diese Art von Beweisführung nicht geeignet. Es ist möglich, aber ziemlich aufwendig. Deswegen wird Haskell im Allgemeinen als Programmiersprache und nicht als Beweisassistent bezeichnet. Agda und Coq hingegen sind richtige Beweisassistenten. Mit denen lässt sich so etwas wesentlich leichter ausdrücken, da es in diesen Sprachen keine scharfe Linie mehr zwischen Typ und Wert gibt.
Die Sache mit den Phantomtypen kann allerdings sehr nützlich sein, um etwa zu beweisen, dass Resourcen nur dort verwendet werden, wo sie auch Sinn ergeben. Du kannst beispielsweise garantieren, dass ein Datei-Handle nach dem dazugehören hClose nicht mehr verwendet werden kann, und zwar statisch. Wie das geht, steht in einem meiner früheren Beispiele.
Ein Phantomtyp ist übrigens einfach nur ein Parameter eines Typs, der nicht verwendet wird. Er dient nur zu Zwecken von statischen Garantien.
Und nur zur Sicherheit: Was verstehst du unter dem Typ einer Funktion? Ich verstehe daunter die Angabe, welche Argument- und Ergebnistypen dieser Funktion (bzw. ihr Definitions- und Wertebereich).
So ist es. Allerdings sind Funktionen in Haskell normale Werte und der Typkonstruktor für Funktionen (→) unterscheidet sich nicht von anderen Typkonstruktoren. Damit gibt Haskell jegliche Sonderstellung von Funktionen auf.
-
knivil schrieb:
Noe, wir sind aus dem Land C++ nach Haskell-Heaven gegangen und berichten euch von unseren Erfahrungen. Es hat mich auch zu einem besseren C++ Programmierer gemacht. Natuerlich ist Ablehnung vorprogrammiert, wie z.B. volkard deutlich zeigt. Die Basis dieser Haltung ist aber eher religioeser Natur als wirklich fundiert. Schade ...
Ich hab mal versucht mit Haskell ein einfaches Anfängerprogramm zu schreiben und fand es ziemlich unschön, was ich hinbekommen hab. Kannst du (oder sonst wer) das Programm von unten mal in Haskell schreiben, um zu zeigen, dass es schön geht.
#include <string> #include <iostream> #include <ctime> int main() { int maxNr = 12; srand(time(0)); int geld = 100; bool ende = false; while ( !ende ) { int setzen = 0; do { std::cout << "\nSie haben " << geld << " Geld. Wieviel wollen Sie setzen? "; std::cin >> setzen; } while (setzen <= 0 || setzen > geld ); int zahl = 0; std::cout << "Auf welche Zahl wollen Sie setzen (1-" << maxNr << ")? "; std::cin >> zahl; int wurf = 1 + rand() % maxNr; std::cout << wurf << " wurde geworfen.\n"; if ( wurf == zahl ) { geld += setzen * 10; } else { geld -= setzen; } if ( geld > 0 ) { std::string weiter; std::cout << "Sie haben " << geld << " Geld. Wollen Sie weiterspielen (j/n)? "; std::cin >> weiter; ende = weiter == "n"; } else { ende = true; } } std::cout << "\nSie haben " << geld << " Geld. Gratulation! ;-)\n"; return 0; }
-
Dieses Beispiel ist in diesem Thread nicht neu. Aber ich gebe naechste Woche meine Diplomarbeit ab. Wird wohl davor nix. Um die Wartezeit zu verkuerzen, kannst du dir gern http://blog.willdonnelly.net/2009/10/14/brians-purely-functional-brain/ ansehen. Dort hast du Zufall, Grafik und Multithreading. Vielleicht willst du das ja mal in C Programmieren?
-
@ertes, volle Zustimmung von mir!
Aber wer noch nie mit funktionalen Programmiersprachen gearbeitet hat, dem kann man schwer diese Denkweise vermitteln.
Schade, daß ich jetzt nicht mehr hier den Sourcecode für meine Diplomarbeit habe (u.a. Programmierung eines kompletten Parsers sowie Verfahren zur Pattern-Matching auf langen (Gen)-Sequenzen in Gofer, einer anderen (älteren) funktionalen Sprache).Auch das Spiel 'Mastermind' hatte ich damals innerhalb von einem halben Tag in Gofer programmiert.
Aber besonders beeindruckend fand ich damals die kurze Def. des QuickSort-Algos:
qs [] = [] qs (x:xs) = qs [y|y<-xs; y<=x] ++ [x] ++ qs [y|y<-xs; y>x]
(ich hoffe, ich habe jetzt hier keinen Tippfehler drin
-
Hab ich mir als Anfängerprogramm zufällig das am schwersten zu programmierende Haskell Programm der Welt ausgesucht oder warum kann hier keiner das in 5 Minuten in Haskel kurz schreiben?
-
knivil schrieb:
Noe, wir sind aus dem Land C++ nach Haskell-Heaven gegangen und berichten euch von unseren Erfahrungen. Es hat mich auch zu einem besseren C++ Programmierer gemacht.
Warst du nicht derjenige, der behauptete, Exceptions wären Teufelszeug und richtige C++-Programmierer würden Fehlercodes benutzen? Ich erinnere mich da an so einen seitenlangen Thread mit Shade Of Mine als Antipoden...
knivil schrieb:
Natuerlich ist Ablehnung vorprogrammiert, wie z.B. volkard deutlich zeigt.
Das ist eine petitio principii. Du /willst/ diese Reaktion, wie deine Wortwahl deutlich zeigt.
knivil schrieb:
Die Basis dieser Haltung ist aber eher religioeser Natur als wirklich fundiert.
Ich glaube, es war ertes, der den religiösen Aspekt von Haskell ins Spiel brachte.
knivil schrieb:
Dieses Beispiel ist in diesem Thread nicht neu. Aber ich gebe naechste Woche meine Diplomarbeit ab. Wird wohl davor nix.
Wohlgemerkt hätte ein halbwegs erfahrener Benutzer jeder beliebigen handelsüblichen Hochsprache dieses Programm in etwa zehn Minuten schreiben können
-
Ich erkenne die Unwahrheit, wenn sie zu schreien beginnt.
http://grander.com/en/products
http://www.nucleostop.de/
http://www.aquapol-sachsen.de/index.php?go=product/advantage
Ihr seid nicht mehr über diesem Niveau, sorry. Es kann keiner von mir verlangen, sich da noch tief einzuarbeiten. Es ist höchst interessant, daß Akkupunktur, Grandnerwasser und Aquapol bei manchen Leuten funktionieren. Aber bei mir definitiv nicht, weil ich nicht an solchen Hokuspokus glaube. Wenn Du persönlich davon begeistert bist, und es Dir geholfen hat, dann bitte. Aber gegen den religiösen Feldzug stehe ich hier und widerspreche.
-
Ich glaube, es war ertes, der den religiösen Aspekt von Haskell ins Spiel brachte.
Ich glaube jeder hat seinen Teil dazu beigetragen.
Wohlgemerkt hätte ein halbwegs erfahrener Benutzer jeder beliebigen handelsüblichen Hochsprache dieses Programm in etwa zehn Minuten schreiben können
Magst du eine Version in Scheme? Ich bin nicht hier um irgendwen zu ueberzeugen, auch wenn es den Anschein hat. Die Muehe von ertes wuerde ich mir nicht machen.
Warst du nicht derjenige, der behauptete, Exceptions wären Teufelszeug und richtige C++-Programmierer würden Fehlercodes benutzen? Ich erinnere mich da an so einen seitenlangen Thread mit Shade Of Mine als Antipoden...
Das hat nur bedingt was mit dem Thema zu tun. In Haskell habe ich wenig ueber Exceptions gelernt. Du kannst gern den entsprechenden Thread verlinken, damit ich selbst nachsehen kann. No review, no study.
Das ist eine petitio principii. Du /willst/ diese Reaktion, wie deine Wortwahl deutlich zeigt.
Nur kam diese Reaktion vor dem was du zitierst. Ich kann diese Logik nicht ganz nachvollziehen. Wie kann ich etwas erbitten, was schon lange eingetreten ist?
Und nu geht es mal wieder nicht mehr um das Thema Haskell ... kein Wunder das der Thread bereits so lang ist.
-
ertes schrieb:
CStoll schrieb:
OK, ich formuliere meine Frage neu: Wie würdest du in Haskell den Typ einer Nachfolger-Funktion oder der Negativ-Funktion (f(x)=-x) beschreiben?
Kommt darauf an, was du beweisen willst. Ohne Beweise sind die Typen ziemlich simpel:
negate :: Num a ⇒ a → a
succ :: Num a ⇒ a → aDas sind auch die Typen, die man in der base-Bibliothek findet. Wenn du hingegen Beweisführung haben willst, gibt es zwei Varianten. Beispiel: Du willst eine Funktion schreiben, die eine nichtnegative Zahl entgegennimmt. Der Typ könnte so aussehen:
sqrt :: PosInt → PosInt
Und was hindert mich nun daran, die succ-Funktion an einer Stelle einzusetzen, an der in meinem Konzept/Spezifikation eigentlich die negate-Funktion erwartet würde?
Eine wirklich sichere Alternative verwendet einen sog. Phantom-Typen:
data NegSign
data NonnegSign
data FlippedSign sdata SInt s where
SZero :: SInt NonnegSign
SSucc :: SInt NonnegSign → SInt NonnegSign
SNegate :: SInt s → SInt (FlippedSign s)class HasSign a where
type SignOf ainstance HasSign (SInt NonnegSign) where
type SignOf (SInt NonnegSign) = NonnegSigninstance HasSign (SInt NegSign) where
type SignOf (SInt NegSign) = NegSigninstance HasSign (SInt (FlippedSign NonnegSign)) where
type SignOf (SInt (FlippedSign NonnegSign)) = NegSigninstance HasSign (SInt (FlippedSign (FlippedSign s))) where
type SignOf (SInt (FlippedSign (FlippedSign s))) = SignOf ssqrt :: (HasSign a, SignOf a ~ NonnegSign) ⇒ a → a
Wie du siehst, ist das schon sehr viel Tipparbeit, um statisch zu garantieren, dass sqrt nur nichtnegative Zahlen erhält. Abgesehen davon ist die Zahlendarstellung, die ich oben gewählt habe, sehr ineffizient. Daran ließe sich etwas machen, aber dann wäre der Code noch länger. Haskell ist für diese Art von Beweisführung nicht geeignet. Es ist möglich, aber ziemlich aufwendig. Deswegen wird Haskell im Allgemeinen als Programmiersprache und nicht als Beweisassistent bezeichnet. Agda und Coq hingegen sind richtige Beweisassistenten. Mit denen lässt sich so etwas wesentlich leichter ausdrücken, da es in diesen Sprachen keine scharfe Linie mehr zwischen Typ und Wert gibt.
Und jetzt mußt du mir nur noch demonstrieren, wie du eine nackte Zahl (von der du zur Compilierzeit nur weißt, daß sie existiert) in dieses HasSign-Konstrukt verpacken willst.
Und nur zur Sicherheit: Was verstehst du unter dem Typ einer Funktion? Ich verstehe daunter die Angabe, welche Argument- und Ergebnistypen dieser Funktion (bzw. ihr Definitions- und Wertebereich).
So ist es. Allerdings sind Funktionen in Haskell normale Werte und der Typkonstruktor für Funktionen (→) unterscheidet sich nicht von anderen Typkonstruktoren. Damit gibt Haskell jegliche Sonderstellung von Funktionen auf.
Das habe ich auch nicht bestritten.
PS: Das Typsystem ist jetzt klarer - und anscheinend liegen die Typ-Definitionen näher an C#-Generics (man muß explizit angeben, was man von den beteiligten Typen erwartet) als an einem C++ Template (die benötigten Operationen ergeben sich aus der Funktionsdefinition). Das erhöht wirklich die Sicherheit der verwendeten Typen, beschränkt aber imho die Flexibilität.
-
knivil schrieb:
Noe, wir sind aus dem Land C++ nach Haskell-Heaven gegangen und berichten euch von unseren Erfahrungen.
Auf diesem Neveau wäre alles ok.
knivil schrieb:
Es hat mich auch zu einem besseren C++ Programmierer gemacht.
Auch ok. Lisp hat mich auch zu einem besseren C++-Programmierer gemacht. Ich empfehle sogar jedem, der in C++ weiterkommen will, einen Ausflug in eine Sprache zu machen, die funktionale Programmierung stark fordert. Und ohne Forth hätte ich nie das Programm zum greorianischen Kalender in Brainfuck hingekriegt.
-
knivil schrieb:
Du kannst gern den entsprechenden Thread verlinken, damit ich selbst nachsehen kann. No review, no study.
Ich glaube kaum, daß du dich an diese 16 Seiten nicht erinnern kannst
http://www.c-plusplus.net/forum/235778knivil schrieb:
Das ist eine petitio principii. Du /willst/ diese Reaktion, wie deine Wortwahl deutlich zeigt.
Nur kam diese Reaktion vor dem was du zitierst.
Pars pro toto.
-
audacia schrieb:
knivil schrieb:
Du kannst gern den entsprechenden Thread verlinken, damit ich selbst nachsehen kann. No review, no study.
Ich glaube kaum, daß du dich an diese 16 Seiten nicht erinnern kannst
http://www.c-plusplus.net/forum/235778Abgesehen dass der Thread die Halbwertzeit erreicht hat.
-
audacia schrieb:
Ich glaube kaum, daß du dich an diese 16 Seiten nicht erinnern kannst
http://www.c-plusplus.net/forum/235778Der erklärt jedenfalls wunderbar, warum knivil jetzt in Haskell so ungemein viel besser programmiert als er es damals in C++ getan hat. Das hatte ich schon aus seinen Postings geschlossen. Hübsch, es nochmal so deutlich lesen zu dürfen.
-
wertzuddsf schrieb:
Ich hab mal versucht mit Haskell ein einfaches Anfängerprogramm zu schreiben und fand es ziemlich unschön, was ich hinbekommen hab. Kannst du (oder sonst wer) das Programm von unten mal in Haskell schreiben, um zu zeigen, dass es schön geht.
Das hier wäre meine Variante, aber die entspricht nicht exakt deiner. Unterschiede: Man kann das Programm überall mit "q" abbrechen, und das Programm reagiert überall mit Fehlermeldung auf ungültige Eingaben. Und falls dir die Typensignatur von prompt Angst macht: Es geht auch einfacher, aber ich versuche immer, meine Funktionen so allgemein wie möglich zu halten. Die prompt-Funktion lässt sich auch in anderen Anwendungen einsetzen:
{-# LANGUAGE ScopedTypeVariables, TypeFamilies #-} module Main where import Control.ContStuff import Control.Exception as Ex import System.IO import System.Random import Text.Printf prompt :: forall a b m. (Abortable m, MonadIO m, Read a, Readable m, Result m ~ StateOf m) => String -> (a -> Maybe b) -> m b prompt p validate = loop where getNextLine = liftIO (putStr p >> hFlush stdout >> getLine) putErrMsg = liftIO (putStrLn "Ungültige Eingabe.") loop :: m b loop = do line <- getNextLine when (line == "q") (get >>= abort) mResult <- liftM (fmap validate) $ liftIO (Ex.try $ readIO line) case mResult of Right (Just x) -> return x Left (_ :: SomeException) -> putErrMsg >> loop _ -> putErrMsg >> loop main :: IO () main = do let (minNum, maxNum, startBalance) = (0, 3, 100 :: Integer) stakePrompt = printf "Sie haben %i Credits.\nEinsatz: " guessPrompt = printf "Tipp (von %i bis %i): " minNum maxNum between minVal maxVal = Just . max minVal . min maxVal putStrLn "Geben Sie \"q\" ein, um das Spiel zu beenden." endBalance <- execStateT startBalance . forever $ do balance <- get when (balance <= 0) (abort 0) liftIO (putChar '\n') stake <- prompt (stakePrompt balance) (between 1 balance) guess <- prompt guessPrompt (between minNum maxNum) num <- liftIO $ randomRIO (minNum, maxNum) liftIO $ do printf "Ihr Einsatz: %i\n" stake printf "Ihr Tipp: %i\n" guess printf "Geworfen wurde: %i\n" num put (balance + (if num == guess then 10*stake else -stake)) printf "Restliche Credits: %i\n" endBalance
Das contstuff-Paket muss installiert sein. Mit dem Befehl
cabal install contstuff
kannst du das bewerkstelligen, wenn du die Haskell-Plattform installiert hast. Das Programm ließe sich sicher mit FRP schöner schreiben, aber damit kenne ich mich leider nicht so gut aus. Auch mit der mitgelieferten transformers-Bibliothek lässt sich das Programm auf ähnliche Weise schreiben, aber ich bin contstuff gewohnt.Th69 schrieb:
Aber besonders beeindruckend fand ich damals die kurze Def. des QuickSort-Algos:
qs [] = [] qs (x:xs) = qs [y|y<-xs; y<=x] ++ [x] ++ qs [y|y<-xs; y>x]
(ich hoffe, ich habe jetzt hier keinen Tippfehler drin
Das Beispiel beeindruckt mich gar nicht. Es sieht zwar an der Oberfläche schön aus, ist aber unglaublich langsam und braucht unglaublich viel RAM. Grund: QuickSort ist für Haskell-Listen überhaupt nicht geeignet. Die Semantik von Listen schreit nach einer MergeSort-Implementierung, und die ist tatsächlich sehr effizient. Mit zwei Zeilen Code kommst du dann allerdings nicht mehr aus. Hier meine MergeSort-Variante:
import Control.Arrow halves :: [a] -> ([a], [a]) halves [] = ([], []) halves [x0] = ([x0], []) halves (x0:x1:xs) = (x0:) *** (x1:) $ halves xs merge :: Ord a => [a] -> [a] -> [a] merge [] ys = ys merge xs [] = xs merge x@(x0:xs) y@(y0:ys) | x0 < y0 = x0 : merge xs y | otherwise = y0 : merge x ys msort :: Ord a => [a] -> [a] msort [] = [] msort [x] = [x] msort xs = uncurry merge . (msort *** msort) . halves $ xs
Die halves-Funktion zerlegt eine Liste in zwei Hälften (allerdings nicht durch einen Schnitt in der Mitte, sondern so: [1,2,3,4,5,6] → ([1,3,5], [2,4,6])). Die merge-Funktion vereinigt zwei bereits sortierte Listen. Und die msort-Funktion führt das alles zusammen zum MergeSort-Algorithmus. Wenn man nur auf kurze Codes geil ist, kann man nur Beispiele bringen, die zwar schön aussehen, aber praktisch nichts taugen. Da habe ich aber vor kurzem etwas viel besseres als QuickSort geschrieben:
import Control.Applicative import Control.Monad import Data.List subseqs :: [a] -> [[a]] subseqs = filterM (const [True, False]) permutations :: [a] -> [[a]] permutations = foldM (flip $ \x -> zipWith (\l r -> l ++ x:r) <$> inits <*> tails) []
Die subseqs-Funktion liefert alle Teillisten einer Liste. Wenn man eine Liste als Menge betrachtet, dann ist subseqs die Potenzmengenfunktion. Die permutations-Funktion liefert alle Permutationen einer Liste. Diese Funktionen sind im Gegensatz zum QuickSort-Beispiel tatsächlich effizient, aber sie sind wohl eher Beispiele davon, wie obskur man mit Monaden programmieren kann. Selbst eingefleischte Haskeller brauchen eine Weile, um diese Beispiele zu verstehen.
CStoll schrieb:
Und was hindert mich nun daran, die succ-Funktion an einer Stelle einzusetzen, an der in meinem Konzept/Spezifikation eigentlich die negate-Funktion erwartet würde?
Im ersten Beispiel gar nichts. Die Garantie ist da nur, dass eine Funktion, die eine Zahl erwartet, auch eine Zahl bekommt, also nicht besonders beeindruckend. Im zweiten Beispiel kannst du aus einer negativen Zahl kein PosInt konstruieren. Allerdings habe ich keine Implementierung zu PosInt angegeben. Auch hier gibt es wieder zwei Möglichkeiten: Entweder du schreibst PosInt als Verpackung von Integer und dazu eine intToPosInt-Funktion, die zur Laufzeit prüft, oder du implementierst PosInt auf eine Art, die negative Zahlen konzeptionell ausschließt, etwa so:
data PosInt = PosChunk Word32 | PosEnd
Falls dir 32 Bits ausreichen, kannst auch auf das Chunking verzichten und dir eine Menge Implementierungsaufwand sparen:
newtype PosInt = PosInt Word32
Der Nachteil, dass man zwei Negierungsfunktionen braucht, bleibt aber bestehen. Besonders schön ist diese Variante also in keinem Fall.
Und jetzt mußt du mir nur noch demonstrieren, wie du eine nackte Zahl (von der du zur Compilierzeit nur weißt, daß sie existiert) in dieses HasSign-Konstrukt verpacken willst.
Eine negative Zahl mit dem Typ SInt NonnegSign ist konzeptionell ausgeschlossen. Diese Prüfung musst du also zur Laufzeit durchführen, bevor die Zahl in das SInt-Konzept eindringt, etwa durch folgende Funktion:
posSInt :: Integer -> Maybe (SInt NonnegSign)
Ist die Zahl negativ, liefert die Funktion Nothing.
Das habe ich auch nicht bestritten.
PS: Das Typsystem ist jetzt klarer - und anscheinend liegen die Typ-Definitionen näher an C#-Generics (man muß explizit angeben, was man von den beteiligten Typen erwartet) als an einem C++ Template (die benötigten Operationen ergeben sich aus der Funktionsdefinition). Das erhöht wirklich die Sicherheit der verwendeten Typen, beschränkt aber imho die Flexibilität.
Im Gegenteil. Das Typensystem von Haskell erlaubt higher order polymorphism, was in C# (und allen anderen .NET-Sprachen) etwa die Übersetzung generic generics hätte und nicht möglich ist. C++ erlaubt eine schwache Form davon und nennt sie template templates. Dieses Konzept ist der Grundstein der in Haskell so beliebten Monaden, denn ohne das wären sie völlig nutzlos.
Dadurch hast du die Flexibilität von C++ kombiniert mit der Sicherheit von C#. In F# hat Microsoft ein OOP-basiertes Workaround gefunden, um doch noch Monaden in das Sprachkonzept mit aufnehmen zu können, aber ohne die Vorteile von Ableitung und Generalisierung, die du in Haskell hast. Du müsstest Haskells mapM-Funktion etwa für jede Monade separat implementieren, und der Code wäre jedes mal der gleiche. In Haskell implementierst du sie einmal für alle Monaden. Das Workaround schließt auch alle erweiterten Konzepte aus, die Monaden als Basis haben, etwa monad transformers. Kurz: In C# (LINQ) und F# (computation expressions) sind Monaden nur ein starres Design-Pattern für ein paar Sonderfälle. Sie kommen nicht annähernd an echte Monaden heran.
-
ertes schrieb:
data PosInt = PosChunk Word32 | PosEnd
Sorry, mein Fehler. Es müsste lauten:
data PosInt = PosChunk Word32 PosInt | PosEnd
-
ertes schrieb:
Im ersten Beispiel gar nichts. Die Garantie ist da nur, dass eine Funktion, die eine Zahl erwartet, auch eine Zahl bekommt, also nicht besonders beeindruckend. Im zweiten Beispiel kannst du aus einer negativen Zahl kein PosInt konstruieren. Allerdings habe ich keine Implementierung zu PosInt angegeben. Auch hier gibt es wieder zwei Möglichkeiten: Entweder du schreibst PosInt als Verpackung von Integer und dazu eine intToPosInt-Funktion, die zur Laufzeit prüft, oder du implementierst PosInt auf eine Art, die negative Zahlen konzeptionell ausschließt, etwa so:
data PosInt = PosChunk Word32 | PosEnd
Falls dir 32 Bits ausreichen, kannst auch auf das Chunking verzichten und dir eine Menge Implementierungsaufwand sparen:
newtype PosInt = PosInt Word32
Der Nachteil, dass man zwei Negierungsfunktionen braucht, bleibt aber bestehen. Besonders schön ist diese Variante also in keinem Fall.
Und jetzt mußt du mir nur noch demonstrieren, wie du eine nackte Zahl (von der du zur Compilierzeit nur weißt, daß sie existiert) in dieses HasSign-Konstrukt verpacken willst.
Eine negative Zahl mit dem Typ SInt NonnegSign ist konzeptionell ausgeschlossen. Diese Prüfung musst du also zur Laufzeit durchführen, bevor die Zahl in das SInt-Konzept eindringt, etwa durch folgende Funktion:
posSInt :: Integer -> Maybe (SInt NonnegSign)
Also lässt sich offenbar doch nicht alles durch den Compiler sicherstellen.
Und außerdem habe ich bei Zahlen auch vor, weiter damit zu rechnen. Also welchen Typ würdest du für die Differenz zweier (positiver) Zahlen angeben.
Und da hier schon einiges über Sortieralgorithmen genannt wurde: Kann der Compiler dir beweisen, daß eine Funktion
(Ordered a) => [a] -> [a]
eine sortierte Kopie des übergebenen Parameters zurückgibt?Im Gegenteil. Das Typensystem von Haskell erlaubt higher order polymorphism, was in C# (und allen anderen .NET-Sprachen) etwa die Übersetzung generic generics hätte und nicht möglich ist. C++ erlaubt eine schwache Form davon und nennt sie template templates. Dieses Konzept ist der Grundstein der in Haskell so beliebten Monaden, denn ohne das wären sie völlig nutzlos.
Ich sagte "näher". C++ Templates verwenden die Operationen, die sie brauchen, und der Compiler beschwert sich wenn die konkreten Template-Parameter sie nicht unterstützen. Bei Haskell und C# muß man explizit angeben, welche Operationen du erwartest und kannst nur diese verwenden.
-
@CStoll: Was zur Laufzeit reinkommt, muss natürlich auch zur Laufzeit geprüft werden. Du kannst aber statisch sicherstellen, dass diese Prüfung korrekt (also der Spezifikation konform) abläuft. Das Weiterrechnen ist in diesem Framework möglich, erfordert aber auch wieder sehr viel Code, weil du auch dafür wieder Klassen und Instanzen brauchst. Wie gesagt: Haskell ist dafür nicht wirklich geeignet. Es ist möglich, aber sehr aufwendig. Da solltest du schon auf einen richtigen Beweisassistenten zugreifen.
Ich sagte "näher". C++ Templates verwenden die Operationen, die sie brauchen, und der Compiler beschwert sich wenn die konkreten Template-Parameter sie nicht unterstützen. Bei Haskell und C# muß man explizit angeben, welche Operationen du erwartest und kannst nur diese verwenden.
Das macht doch keinen Flexibilitätsunterschied. Du musst halt in Haskell mehr spezifizieren und hast am Ende wahrscheinlich trotzdem weniger Code, weil C++ im Vergleich ziemlich viel Rauschen enthält.