Haskell



  • 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.



  • ertes schrieb:

    @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.

    Und im Endeffekt muß ich diese Prüfung doch bei jeder Funktion durchführen, die nicht auf dem gesamten Wertebereich definiert ist - Wurzel, Logarithmus, Arcus-Funktionen etc. (abgesehen davon hängen die Beschränkungen auch davon ab, welchen Wertebereich du verwendest)

    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.

    Was meinst du mit "Rauschen"? Die Tatsache, daß man in C++ lesbare Schlüsselwörter wie template oder return verwendet? Das erhöht aber auch die Lesbarkeit des Codes.



  • ertes schrieb:

    weil C++ im Vergleich ziemlich viel Rauschen enthält.

    Rauschen ist nicht das richtige Wort. Rauschen klingt nach unerwünschten Funkstörungen. In C++ würde man mehr Zeichen tippen müssen, aber die rauscht man nicht in die Tastatur rein. Redundaz ist nicht Rauschen.



  • volkard schrieb:

    ertes schrieb:

    weil C++ im Vergleich ziemlich viel Rauschen enthält.

    Rauschen ist nicht das richtige Wort. Rauschen klingt nach unerwünschten Funkstörungen. In C++ würde man mehr Zeichen tippen müssen, aber die rauscht man nicht in die Tastatur rein. Redundaz ist nicht Rauschen.

    Ihm rauscht es aber von seine funktionale Sicht der Dinge. Seine Herleitung sind subjektiv, deswegen schmunzeln ich lieber darüber.



  • Zeus schrieb:

    Ihm rauscht es aber von seine funktionale Sicht der Dinge. Seine Herleitung sind subjektiv, deswegen schmunzeln ich lieber darüber.

    Ja, soo gesehen...
    Ich finde, daß nur "<>+-.,[]" Information tragen, Buchstaben sind Rauschen. Wobei ich betone, daß ich Buchstaben gerne für Kommentare einsetze und sie für sinnvoll halte.



  • Die Tatsache, daß man in C++ lesbare Schlüsselwörter wie template oder return verwendet? Das erhöht aber auch die Lesbarkeit des Codes.

    Ja, aber Haskell geht nunmal einen anderen Weg.

    Man lässt z.B. einfach beim Funktionsaufruf die Klammern weg, da Funktionsaufrufe das sind, was in einer funktionalen Sprache am häufigsten vorkommt. Genauso muss man im Funktionskopf eines Templates die Template-Parameter nicht mit dem Schlüsselwort "template" einführen, sondern über die Regel, dass nur Typen mit einem Großbuchstaben beginnen, ist klar, dass ein kleiner Buchstabe in der Funktionsdefinition eine Typvariable ist:

    Funktion von Float nach Int:
    f :: Float -> Int

    Funktion von irgendeinem Typ nach Int:
    f :: a -> Int

    Solche Sachen finden sich ständig in Haskell. Z.B. ist auch die Lambda-Syntax wunderbar einfach:
    f = \x -> ...

    Oder auch die Möglichkeit lokal begrenzte Funktionen/Werte mit "where" einzuführen.
    Alles Dinge, die die Lesbarkeit stark erhöhen und Haskell für mich zu einer sehr schönen und eleganten Sprache machen.



  • CStoll schrieb:

    Und im Endeffekt muß ich diese Prüfung doch bei jeder Funktion durchführen, die nicht auf dem gesamten Wertebereich definiert ist - Wurzel, Logarithmus, Arcus-Funktionen etc. (abgesehen davon hängen die Beschränkungen auch davon ab, welchen Wertebereich du verwendest)

    Falsch. Diese Prüfung führst du an der Stelle durch, in der Zahlen das Konstrukt betreten. Zum Zeitpunkt, an dem die genannten Funktionen aufgerufen werden, hat diese Prüfung schon lange stattgefunden. Tatsächlich kannst du keine sqrt-Funktion schreiben, die nicht mit NonnegSign definiert ist.

    Was meinst du mit "Rauschen"? Die Tatsache, daß man in C++ lesbare Schlüsselwörter wie template oder return verwendet? Das erhöht aber auch die Lesbarkeit des Codes.

    Ich finde Haskell nicht schwerer zu lesen trotz weniger Rauschen. "Rauschen" ist übrigens eine direkte Übersetzung von "line noise", da mir kein besserer Begriff eingefallen ist. Es ist nicht zwangsläufig etwas Negatives, aber es ist halt mehr zu tippen und mehr zu lesen für weniger Information. Beispiel:

    template <template <typename> class F, typename A, typename B> F<B> someFunc(B (*f)(A), F<A> c) {
        …
    }
    

    Haskell:

    someFunc :: Functor f => (a -> b) -> f a -> f b
    someFunc f c = …
    

    C# etwa verbessert diese Syntax dramatisch, aber da kann man die o.g. Funktion erst gar nicht ausdrücken, da es keine generic generics gibt. Man muss sich da auf OOP und dynamisches Casting verlassen, was sehr unschön ist, selbst wieder sehr viel Rauschen reinbringt und außerdem fehleranfälliger ist.

    Irgendwer schrieb:

    Man lässt z.B. einfach beim Funktionsaufruf die Klammern weg, da Funktionsaufrufe das sind, was in einer funktionalen Sprache am häufigsten vorkommt. Genauso muss man im Funktionskopf eines Templates die Template-Parameter nicht mit dem Schlüsselwort "template" einführen, sondern über die Regel, dass nur Typen mit einem Großbuchstaben beginnen, ist klar, dass ein kleiner Buchstabe in der Funktionsdefinition eine Typvariable ist: […]

    Es gibt auch Alternativen zu C++, deren Syntax etwas besser ist, aber trotzdem fehlt mir in diesen Sprachen einfach die prägnante Syntax und die Typinferenz. In C++ musst du oft a<B> schreiben, obwohl aus dem Kontext völlig klar ist, was B ist. Es ist nicht die Redundanz, die mich stört, sondern die unnötige Redundanz. Das ist das, was ich als Rauschen sehe.

    Außerdem bevorzuge ich Kombinatoren den expliziten Kontrollstrukturen wie Schleifen. Zu letzteren zähle ich übrigens auch explizite Rekursion. Die braucht man in Haskell selten. Und schließlich fehlt mir in C++ die Möglichkeit, Koroutinen zu schreiben.



  • ertes schrieb:

    Es ist nicht die Redundanz, die mich stört, sondern die unnötige Redundanz. Das ist das, was ich als Rauschen sehe.

    Bitte nenne sie unnötige Redundanz. Rauschen ist ein Schmähwort. Auch bei den Amis.



  • ertes schrieb:

    CStoll schrieb:

    Und im Endeffekt muß ich diese Prüfung doch bei jeder Funktion durchführen, die nicht auf dem gesamten Wertebereich definiert ist - Wurzel, Logarithmus, Arcus-Funktionen etc. (abgesehen davon hängen die Beschränkungen auch davon ab, welchen Wertebereich du verwendest)

    Falsch. Diese Prüfung führst du an der Stelle durch, in der Zahlen das Konstrukt betreten. Zum Zeitpunkt, an dem die genannten Funktionen aufgerufen werden, hat diese Prüfung schon lange stattgefunden. Tatsächlich kannst du keine sqrt-Funktion schreiben, die nicht mit NonnegSign definiert ist.

    Hast du schonmal daran gedacht, daß nicht jede Funktion garantieren kann, daß ihr Rückgabewert positiv ist? Einfachstes Beispiel ist die Subtraktion, also wie würdest du die schreiben, um f(x,y)=sqrt(x-y) ohne Laufzeitprüfungen berechnen zu können?

    In C++ musst du oft a<B> schreiben, obwohl aus dem Kontext völlig klar ist, was B ist.

    Der Compiler hat eventuell ein anderes Verständnis von Kontext als du 😉 Und außerdem habe ich gehört, daß es im neuesten C++ Standard das Schlüsselwort auto geben soll, daß diese redundanten Typ-Nennungen reduziert.

    Es ist nicht die Redundanz, die mich stört, sondern die unnötige Redundanz. Das ist das, was ich als Rauschen sehe.

    Und wo ziehst du die Grenze zwischen nötiger und unnötiger Redundanz?

    PS: Meine Frage nach der Validierung von Sortieralgorithmen durch den Compiler hast du auch nicht beantwortet.



  • vor kurzem etwas viel besseres als QuickSort geschrieben .. Potenzmengenfunktion .. permutations

    a) wenn in der Typsignatur Nur mit Listen gearbeitet wird. warum nutzt du dann foldM, filterM etc. b) diese Funktionen sind bereits in List enthalten, sie brauchen nicht selbst implementiert werden. Und das Verstaendnisproblem ruehert daher, dass sie eben nicht kanonisch implementiert sind.



  • CStoll schrieb:

    Hast du schonmal daran gedacht, daß nicht jede Funktion garantieren kann, daß ihr Rückgabewert positiv ist? Einfachstes Beispiel ist die Subtraktion, also wie würdest du die schreiben, um f(x,y)=sqrt(x-y) ohne Laufzeitprüfungen berechnen zu können?

    Deine Funktion f kompiliert einwandfrei. Sie kann nur nicht mit jedem Argumentpaar ausgeführt werden. Wenn der Compiler beweisen kann, dass x - y niemals negativ wird, also dass x ≥ y ist, dann ist auch dein Funktionsaufruf gültig. Falls du das nicht statisch sicherstellen kannst, musst du das eben zur Laufzeit machen. Das lässt sich machen, aber es wird mir langsam zu anstrengend, für alles Codebeispiele zu liefern.

    Ich habe mehrfach gesagt, dass Haskell für diese Art von statischen Garantien nicht geeignet ist. Es wird Zeit, dass du das berücksichtigst. Wenn du solche Garantien willst, musst du Agda oder Coq nehmen.

    Der Compiler hat eventuell ein anderes Verständnis von Kontext als du 😉 Und außerdem habe ich gehört, daß es im neuesten C++ Standard das Schlüsselwort auto geben soll, daß diese redundanten Typ-Nennungen reduziert.

    Das ist richtig. Typinferenz in Haskell ist global, und in Standard-H98 musst du normalerweise keine Typensignaturen schreiben. Aber ich sehe es als guten Programmierstil an, sie trotzdem anzugeben, da die Typen deine Spezifikation sind.

    Und wo ziehst du die Grenze zwischen nötiger und unnötiger Redundanz?

    Sobald sie sich anfühlt wie Boilerplate ist sie für mich unnötig.

    PS: Meine Frage nach der Validierung von Sortieralgorithmen durch den Compiler hast du auch nicht beantwortet.

    Habe ich überlesen, sorry. Sortiertheit ist eigentlich relativ einfach statisch zu garantieren. Das habe ich schon mal gemacht via GADTs.

    knivil schrieb:

    a) wenn in der Typsignatur Nur mit Listen gearbeitet wird. warum nutzt du dann foldM, filterM etc. b) diese Funktionen sind bereits in List enthalten, sie brauchen nicht selbst implementiert werden. Und das Verstaendnisproblem ruehert daher, dass sie eben nicht kanonisch implementiert sind.

    Du verwechselst da was. Die Funktionen filterM und foldM sind Verallgemeinerungen von filter und foldl. Das erkennt man, wenn man die Typensignaturen vergleicht:

    filter :: (a → Bool) → [a] → [a]
    filterM :: Monad m ⇒ (am Bool) → [a] → m [a]

    foldl :: (bab) → b → [a] → b
    foldM :: Monad m ⇒ (bam b) → b → [a] → m b

    Die Funktionen sind äquivalent in der Identitätsmonade (Id m ist isomorph zu m), können sich in anderen Monaden aber drastisch voneinander unterscheiden. Meine Code-Beispiele bewegen sich in der Listenmonade, also ist m = []. Definiert sind die Funktionen in Control.Monad.



  • die Haskell Version des Glückspiel-Programms ist länger und für Nicht-Haskellaner schwerer zu verstehen als es die C++ Version für Nicht-C++er ist. Außerdem sehe ich in der haskell-Version 4-mal "do" ... nicht gerade das typischste Element funktionaler Programmierung.

    Gut, der Glücksspiel-Algo ist allerdings auch ein "Heimspiel" für klassische imperative Programmierung. Eine ganz andere Problemstellung, die beispielsweise zustandsfreie rekursive Datenstrukturen verlangte, wäre da eine bessere "Reklame" für Funk Prog.

    Trotzdem ist das Beispiel nicht geeignet, meine Skepsis über die Vorteile von Funk Prog bei "real world"-Problemstellungen zu verringern.



  • ertes schrieb:

    CStoll schrieb:

    Hast du schonmal daran gedacht, daß nicht jede Funktion garantieren kann, daß ihr Rückgabewert positiv ist? Einfachstes Beispiel ist die Subtraktion, also wie würdest du die schreiben, um f(x,y)=sqrt(x-y) ohne Laufzeitprüfungen berechnen zu können?

    Deine Funktion f kompiliert einwandfrei. Sie kann nur nicht mit jedem Argumentpaar ausgeführt werden. Wenn der Compiler beweisen kann, dass x - y niemals negativ wird, also dass x ≥ y ist, dann ist auch dein Funktionsaufruf gültig. Falls du das nicht statisch sicherstellen kannst, musst du das eben zur Laufzeit machen. Das lässt sich machen, aber es wird mir langsam zu anstrengend, für alles Codebeispiele zu liefern.

    Ich habe mehrfach gesagt, dass Haskell für diese Art von statischen Garantien nicht geeignet ist. Es wird Zeit, dass du das berücksichtigst. Wenn du solche Garantien willst, musst du Agda oder Coq nehmen.

    Das heißt also, sobald die verwendeten Formeln etwas komplexer werden, muß ich doch wieder manuell prüfen ob die Parameter korrekt sind.

    Der Compiler hat eventuell ein anderes Verständnis von Kontext als du 😉 Und außerdem habe ich gehört, daß es im neuesten C++ Standard das Schlüsselwort auto geben soll, daß diese redundanten Typ-Nennungen reduziert.

    Das ist richtig. Typinferenz in Haskell ist global, und in Standard-H98 musst du normalerweise keine Typensignaturen schreiben. Aber ich sehe es als guten Programmierstil an, sie trotzdem anzugeben, da die Typen deine Spezifikation sind.

    Aber aus den Typen alleine kann man trotzdem nicht alle herleiten - d.h. wenn du in einer komplexeren Formel ein Vorzeichen verwechselt oder eine Klammer falsch gesetzt hast, siehst du trotzdem erst zur Laufzeit, daß das Ergebnis von deinen Erwartungen abweicht. Wie findest du dann heraus, wo der Fehler lag? (in imperativen oder objektorientierten Sprachen würde ich dazu den Debugger nutzen)

    Außerdem: Wenn ich nur die Funktionsdefinition ohne explizite Typangaben schreibe, woher weiß der Compiler dann, welche Bedingungen er an die beteiligten Typen stellen soll?

    Und wo ziehst du die Grenze zwischen nötiger und unnötiger Redundanz?

    Sobald sie sich anfühlt wie Boilerplate ist sie für mich unnötig.

    Und wie fühlt sich "Boilerplate" an? Es mag ja sein, daß ich daran gewöhnt bin, aber mich stört es nicht, daß die explizite Angabe der Template-Parameter in C++ ein wenig länger wird als in Haskell.

    PS: Meine Frage nach der Validierung von Sortieralgorithmen durch den Compiler hast du auch nicht beantwortet.

    Habe ich überlesen, sorry. Sortiertheit ist eigentlich relativ einfach statisch zu garantieren. Das habe ich schon mal gemacht via GADTs.

    Im Werfen mit (unbekannten) Begriffen bist du schon Weltklasse 😃
    Nehmen wir mal an, ich schreibe eine Funktion vom Typ (Ordered a) => [a] -> [a] und behaupte, daß für beliebige Eingaben (die zur Compilezeit unbekannt sind) diese Funktion immer eine aufsteigend sortierte Liste zurückgibt, die die selben Einträge enthält wie die Eingabe. Wie kann ich diese Behauptung durch den Compiler beweisen lassen?

    [quote="knivil"][...]

    Du verwechselst da was. [...]

    Es ist interessant zu sehen, daß jetzt schon erste Meinungsverschiedenheiten innerhalb der Haskell-Fraktion auftreten 😃



  • ertes schrieb:

    CStoll schrieb:

    Hast du schonmal daran gedacht, daß nicht jede Funktion garantieren kann, daß ihr Rückgabewert positiv ist? Einfachstes Beispiel ist die Subtraktion, also wie würdest du die schreiben, um f(x,y)=sqrt(x-y) ohne Laufzeitprüfungen berechnen zu können?

    Deine Funktion f kompiliert einwandfrei. Sie kann nur nicht mit jedem Argumentpaar ausgeführt werden. Wenn der Compiler beweisen kann, dass x - y niemals negativ wird, also dass x ≥ y ist, dann ist auch dein Funktionsaufruf gültig. Falls du das nicht statisch sicherstellen kannst, musst du das eben zur Laufzeit machen.

    Also sind diese Tricks für echtes Programmieren gar nicht geeignet, sondern nur um zum Beispiel sicherzustellen, daß MAIL FROM vor EHLO gesendet wird. Ich muß sagen, daß diese Reihenfolge (oder erst Adresse holen, dann erst Adresse verwenden) für mich nie ein Problem darstellte.



  • die Haskell Version des Glückspiel-Programms ist länger und für Nicht-Haskellaner schwerer zu verstehen

    Das Beispiel wurde ja auch so ausgewaehlt, um genau das zu demonstrieren. Deswegen habe ich mich auch geweigert eine Implementation anzugeben, da dann ueber Haskell an diesem einen Beispiel gerichtet wird.

    Es ist interessant zu sehen, daß jetzt schon erste Meinungsverschiedenheiten innerhalb der Haskell-Fraktion auftreten

    Wenn es zwei Loesungen A und B fuer ein Problem gibt, werden manche A und manche B waehlen.



  • 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]
    

    In Smalltalk ist quicksort auch ein Einzeiler, und smalltalk ist eigentlich eine OOP-Sprache:

    Object class compile:'q:a a size<=1 ifTrue:[^a].^(self q:(a allButFirst select:[:i|i<a first])),a first asArray,(self q:(a allButFirst select:[:i|i>=a first]))'.
    
    Beispiel: 
    
    Object q: #(8 4 5 3 2 1 4 7). "#(1 2 3 4 4 5 7 8)"
    

    ertes schrieb:

    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.

    Im von mir benutzten Smalltalk-System schon: 😃

    #(3 8 1 3 5 4) mergeSortFrom: 1 to: 6 by: [ :x :y | x <= y ]. "#(1 3 3 4 5 8)"
    


  • !rr!rr_. schrieb:

    ertes schrieb:

    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.

    Im von mir benutzten Smalltalk-System schon: 😃

    #(3 8 1 3 5 4) mergeSortFrom: 1 to: 6 by: [ :x :y | x <= y ]. "#(1 3 3 4 5 8)"
    

    Nee, es ging nicht um den Aufruf, der in eine Zeile paßt, sondern um die Definition.



  • In Smalltalk ist quicksort auch ein Einzeiler, und smalltalk ist eigentlich eine OOP-Sprache:

    Natuerlich kannst du alles in einer Zeile schreiben. Unter dieser Praemisse ist jedes C oder C++ Programm ein Einzeiler. 🙄 Und was das mit OOP zu tun hat, ist auch fraglich.



  • @knivil: Darf ich dich auf eine deiner früheren Aussagen aufmerksam machen?

    knivil schrieb:

    gegen alle Probleme der (Programmierer)Welt ist

    Bitte nenne die Probleme und ich kann dir sagen, wie Haskell im Vergleich zu imperativen Sprachen hilft.

    -> da hat dir jemand ein Beispiel geliefert, um diese Aussage zu bestätigen - und du erklärst einfach, das Beispiel sei unpassend 🙄 so kann man sich auch aus der Affäre ziehen.

    Auf meinen Vorschlag mit dem Telefonbuch bist du auch nicht näher eingegangen. Es muß ja kein komplettes Programm mit grafischer Oberfläche sein, mir würde da schon die grundlegende Programmlogik ausreichen, um folgende Operationen zu unterstützen:

    • insert(name,telefon)
      trägt den Datensatz aus 'name' und 'telefon' in die Datenbasis ein (zusammen mit einer eindeutigen Kennung)
    • update(id,name,telefon)
      ersetzt den Inhalt des Datensatzes mit der Kennung id durch die übergebenen Werte
      (alternativ auch update_name(id,name) und update_tel(id,telefon)
    • delete(id)
      löscht den Datensatz mit der Kennung id
    • find_name(name) und find_tel(telefon)
      liefert eine Liste aller Datensätze, die den angegebenen Namen bzw. Telefonnummer enthalten

Anmelden zum Antworten