it-swarm.com.de

Verwenden Sie ein "starkes" System in der realen Welt, beispielsweise für große Web-Apps?

Ich weiß, dass dies eine sehr breite, mehrdeutige und möglicherweise philosophische Frage ist. Bis zu einem gewissen Grad ist das wichtigste Schlüsselwort in der Frage - "starkes" Typsystem - selbst schlecht definiert . Lassen Sie mich versuchen zu erklären, was ich meine.

Gesamtkontext für die Frage

Wir haben eine sehr große Web-App in Ruby on Rails) erstellt und waren im Allgemeinen glücklich über unseren Stack. Wenn wir wollen, Wir können Sachen sehr schnell versenden - etwas, das für 90% des "Geschäfts" -Falls funktioniert, ohne sich über 10% der Edge-Fälle Gedanken zu machen. Auf der anderen Seite können wir dies mithilfe von Code-Reviews und Test-Coverage tun Langsam und absichtlich und stellen Sie sicher, dass wir alle Grundlagen abdecken - wiederum nur in Situationen, die eine genauere Prüfung und Sicherheit verdienen.

Als das Team wächst, fühle ich mich jedoch unwohl mit dem Fehlen eines "Sicherheitsnetzes", das direkt in unseren Stapel eingebrannt ist.

Wir haben kürzlich begonnen, einige native Android -Entwicklungen auf Java durchzuführen. Und ich wurde (angenehm) an die Sicherheit erinnert, die eine kompilierte/statische/stark typisierte Sprache bietet.

  • Falsch geschriebene Variablen, falsche Datentypen, falsche Funktionsaufrufe und eine Vielzahl trivialer Fehler werden von Ihrer IDE selbst) abgefangen. Alles nur, weil die IDE) einbinden kann in den Compiler und überprüfen Sie bestimmte Aspekte des Programms "Korrektheit".
  • Müssen Sie eine Funktionssignatur ändern? Einfach. Der Compiler + IDE kann Ihnen helfen, ALLE Aufrufstellen zu erkennen.
  • Müssen Sie sicherstellen, dass bestimmte Ausnahmen immer behandelt werden? Überprüfte Ausnahmen zu Ihrer Rettung.

Obwohl diese Sicherheitsmerkmale ihre Vorteile haben, bin ich mir auch ihrer Nachteile bewusst. Mehr noch, in der Welt von "Boilerplate Heavy" Java. Daher habe ich mich anstelle von Java mit den modernen "stark typisierten" Sprachen befasst, an denen die Leute heutzutage zu arbeiten begonnen haben. Zum Beispiel: Scala, Rust, Haskell usw. Was mich am meisten interessiert, ist die Leistungsfähigkeit ihrer Typsysteme und die Überprüfung der statischen/Kompilierungszeit.

Nun die Frage

Wie verwende ich diese leistungsstarken Typsysteme und statischen Funktionen/Funktionen zur Kompilierungszeit in größeren Anwendungen?

Wie würde ich zum Beispiel über die üblichen "Hallo Welt" -Einführungen in diese leistungsstarken Funktionen hinausgehen? Eines, das ein Rich-Type-System verwendet, um ein Business-Domain-Problem zu modellieren? Hilft oder behindert das Typsystem, wenn Sie sich in der 30.000 LOC + -Zone befinden? Was passiert mit dem Sicherheitsnetz dieser Typsysteme (und Überprüfungen zur Kompilierungszeit), wenn Ihr System mit der schwach typisierten Außenwelt interagiert, z. über JSON- oder XML-APIs, verschiedene Datenspeicher, Benutzereingaben usw.

29
Saurabh Nanda

Ich werde wegen meines Zeitmangels im Moment eine kurze Antwort geben, aber ich arbeite derzeit an zwei großen Projekten (> 100.000 LOC in Haskell) - flowbox.io und luna-lang.org . Wir verwenden Haskell für alle Teile, einschließlich des Backends, des Compilers unserer Programmiersprache und sogar der webGL-basierten GUI. Ich muss zugeben, dass das starke Typensystem und die "abhängigen Typ" -ähnlichen Maschinen Sie führen und Sie vor Belastungen und Problemen bewahren können, die aus anderen Sprachen bekannt sind. Wir verwenden die Typen sehr häufig und alles, was in der Kompilierungszeit überprüft werden konnte, wird so gemacht. Tatsächlich sind wir in den letzten 3 Jahren der Entwicklung nie auf einen Laufzeitfehler oder einen Stapelüberlauf gestoßen (und das ist etwas wirklich Unglaubliches). Die einzigen Fehler sind offensichtliche Logikfehler von Programmierern. Viele Leute sagen, wenn etwas in Haskell kompiliert wird, funktioniert es einfach und Sie sollten ziemlich sicher sein, dass es Ihnen eines Tages nicht ins Gesicht bläst. Dies gilt für die meisten Situationen. Wenn Sie die Sprache gut kennen und wissen, was zu vermeiden ist (wie nicht implementierte Typklassenmethoden), sind Sie sicher und erzielen große Gewinne mit dem Typensystem.

Beantwortung des ersten Teils der Frage: Sie können mehr über diese leistungsstarken Systemfunktionen erfahren, indem Sie einige großartige Blogs lesen, wie zum Beispiel:

Tatsächlich gibt es viele andere nette Blogs (wie Planet Haskell ). Die beste Methode, um die fortschrittlichen Typsysteme wirklich zu verstehen, ist die Entwicklung einer nützlichen Open-Source-Bibliothek. Wir (bei Flowbox & New Byte Order) veröffentlichen viele Bibliotheken (Sie finden sie bei Hackage). Wenn Sie also keine Ahnung haben, was Sie entwickeln sollen, können Sie sich jederzeit an unseren Projekten beteiligen - senden Sie mir einfach eine E-Mail, wann immer Sie möchten wollen (Mail verfügbar unter luna-lang.org ).

34
danilo2

Nun, schwaches oder starkes Tippen ist ziemlich vage definiert. Da es einer allgemeinen Verwendung von "starker Typisierung" am nächsten kommt, sich auf Dinge zu beziehen, die das Gießen von Typen erschweren, bleibt nichts weiter übrig, um noch stärkere Typsysteme zu beschreiben. Es ist wie zu sagen, wenn Sie weniger als 30 Pfund tragen können, sind Sie schwach, und jeder, der mehr heben kann, gehört zur gleichen Kategorie von "stark" - eine irreführende Unterscheidung.

Also bevorzuge ich die Definition:

  • Schwach typisierte Systeme verwenden Typen, um zu verhindern, dass Sie bestimmte Dinge tun (z. B. Fehler).
  • Stark typisierte Systeme verwenden Typen, um Dinge für Sie zu tun

Was meine ich damit, Dinge für dich zu tun? Lassen Sie uns das Schreiben einer Bildkonvertierungs-API im Servant-Framework untersuchen (in Haskell, aber Sie müssen es nicht wirklich wissen, um mitzumachen, Sie werden sehen ...)

{-# LANGUAGE
    TypeOperators,
    DataKinds
    #-}

import Codec.Picture
import Data.Proxy
import Network.Wai.Handler.Warp (run)
import Servant
import Servant.JuicyPixels

main :: IO ()
main = run 8001 conversion

Dies bedeutet, dass wir einige Module einschließlich des Servant-Pakets und des JuicyPixels-Plugins für Servant benötigen und dass der Haupteinstiegspunkt des Programms darin besteht, die Konvertierungsfunktion auf Port 8001 als Server über das Warp-Backend auszuführen. Ignorieren Sie das Sprachbit.

conversion :: Application
conversion = serve (Proxy :: Proxy ConversionApi) handler

Dies bedeutet, dass die Konvertierungsfunktion ein Server ist, auf dem die API dem Typ 'ConversionApi' entsprechen muss und die Anforderungen von der Funktion handler verarbeitet werden

type ConversionApi
     = ReqBody '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage
    :> Post '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage

Dies gibt den Typ ConvesionApi an. Es heißt, wir sollten eingehende Inhaltstypen akzeptieren, die in der Liste '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] angegeben sind, und sie als DynamicImage behandeln und ein DynamicImage zurückgeben, das in denselben Inhaltsbereich konvertiert wurde Typen. Mach dir keine genauen Gedanken darüber, was:> bedeutet, betrachte es vorerst als glückliche Magie.

In Anbetracht meiner bevorzugten Definition kann ein schwach typisiertes System nun Folgendes sicherstellen:

  • Sie geben nicht den falschen ausgehenden Inhaltstyp zurück
  • Sie analysieren die eingehende Anforderung nicht als den falschen Inhaltstyp
  • Wenn unser Server komplizierter wäre, würde dies verhindern, dass wir fehlerhafte URIs erstellen, aber wir geben keine HTML-Seiten zurück, die Links enthalten (und der Typ stellt sicher, dass wir dies nicht können!).
  • Ein wirklich ehrgeiziges schwaches Typisierungssystem überprüft möglicherweise sogar, ob alle eingehenden und ausgehenden Inhaltstypen vollständig verarbeitet werden, sodass der Typ auch als Spezifikationsdokument und nicht nur als Einschränkung fungieren kann.

Alle hohen Ziele, aber nicht genug, um sich angesichts der obigen Definition als stark typisiertes System zu qualifizieren. Und jetzt müssen wir zum harten Teil des tatsächlichen Schreibens von Code gelangen, der dieser Spezifikation folgt. In einem wirklich starken Typsystem schreiben wir:

handler = return

Und dann sind wir fertig. Das war's, es gibt keinen Code mehr zum Schreiben. Dies ist ein voll funktionsfähiger Webserver (modulo alle Tippfehler, die ich verpasst habe). Der Typ hat dem Compiler alles mitgeteilt, was er zum Erstellen unseres Webservers aus den von uns definierten und importierten Typen und Paketen (technischen Modulen) benötigt.

Wie lernen Sie dies auf der Hauptanwendungsskala? Nun, es unterscheidet sich nicht wesentlich von der Verwendung in kleineren Anwendungen. Absolute Typen kümmern sich nicht darum, wie viel Code in Bezug auf sie geschrieben wird.

Die Typprüfung zur Laufzeit ist etwas, das Sie wahrscheinlich vermeiden möchten, da dadurch ein großer Teil des Nutzens eingespart wird und Typen die Arbeit mit Ihrem Projekt komplizierter gestalten können, anstatt dass Typen die Dinge vereinfachen.

Als solches ist es meistens nur eine Frage der Übung, Dinge mit Typen zu modellieren. Die beiden Hauptmethoden zum Modellieren von Dingen (oder zum Erstellen von Dingen im Allgemeinen) sind Bottom-Up und Top-Down. Top-down beginnt mit der höchsten Ebene von Operationen, und während Sie das Modell erstellen, haben Sie Teile, in denen Sie die Modellierung bis später verschieben. Bottom-up-Modellierung bedeutet, dass Sie mit Basisoperationen beginnen, genau wie Sie mit Basisfunktionen beginnen, und dann immer größere Modelle erstellen, bis Sie die Operation des Projekts vollständig erfasst haben. Bottom-up ist konkreter und wahrscheinlich schneller zu erstellen, aber Top-down kann Ihre Modelle auf niedrigerer Ebene besser darüber informieren, wie sie sich tatsächlich verhalten müssen.

Typen sind, wie Programme sich buchstäblich auf Mathematik beziehen, also gibt es nicht wirklich eine Obergrenze dafür, wie kompliziert sie werden können, oder einen Punkt, an dem Sie "fertig" sein können, um etwas über sie zu lernen. Praktisch alle Ressourcen außerhalb von Universitätskursen auf höherer Ebene sind darauf ausgerichtet, wie Typen in einer bestimmten Sprache funktionieren. Sie müssen sich also auch dafür entscheiden.

So gut ich kann, können Typen wie folgt geschichtet werden:

  • Sehr schwach getippt, Dinge wie JavaScript, bei denen [] + {} definiert ist
  • Schwach getippt wie Python, wo Sie nicht [] + {} tun können, aber das wird erst überprüft, wenn Sie es versuchen
  • Schwach getippt wie C oder Java, wo Sie nicht [] + {} ausführen können, dies wird jedoch beim Kompilieren überprüft, Sie verfügen jedoch nicht über die erweiterten Typfunktionen
  • Überspannen Sie die Grenze zwischen schwach und stark typisiert, wie z. B. C++ - Vorlagen-Metaprogrammierung, und einfacherem Haskell-Code, bei dem Typen nur Eigenschaften erzwingen.
  • Voll in stark typisiert, wie kompliziertere Haskell-Programme, in denen Typen Dinge tun, wie oben gezeigt
  • Die sehr stark typisierten, wie Agda oder Idris, wo Typen und Werte interagieren und sich gegenseitig einschränken können. Dies ist so stark wie Typsysteme, und das Programmieren in ihnen ist dasselbe wie das Schreiben mathematischer Beweise darüber, was Ihr Programm tut. Hinweis: Codierung in Agda ist nicht wörtlich mathematische Beweise schreiben, Typen sind mathematische Theorien, und Funktionen mit diesen Typen sind konstruktive Beispiele, die diese Theorien beweisen.

Je weiter unten Sie auf dieser Liste stehen, desto mehr können Typen für Sie tun, aber ganz unten klettern Sie in die Stratosphäre und die Luft wird etwas dünner - das Paket-Ökosystem ist viel kleiner und Sie ' Ich muss mehr Dinge selbst schreiben, als eine relevante Bibliothek gefunden zu haben. Die Eintrittsbarriere steigt auch, wenn Sie nach unten gehen, da Sie das Typensystem tatsächlich genug verstehen müssen, um große Programme zu schreiben.

17

Ich habe gerade angefangen, im Kernteam einer großen Plattform zu arbeiten, die in Scala geschrieben wurde. Sie können sich erfolgreiche Open Source-Anwendungen wie Scalatra, Play oder Slick ansehen, um zu sehen, wie sie einige Ihrer detaillierteren Fragen zu Interaktionen mit dynamischen Datenformaten behandeln.

Einer der großen Vorteile, die wir bei der starken Typisierung von Scala) gefunden haben, liegt in der Benutzererziehung. Das Kernteam kann Entscheidungen treffen und diese Entscheidungen im Typensystem durchsetzen, also wenn andere Teams dies tun Viel weniger vertraut mit den Entwurfsprinzipien, die mit dem System interagieren müssen, der Compiler korrigiert sie und das Kernteam korrigiert nicht ständig Dinge in Pull-Anforderungen. Dies ist ein riesiger Vorteil in einem großen System.

Natürlich können nicht alle Entwurfsprinzipien in einem Typsystem erzwungen werden. Je stärker Ihr Typsystem ist, desto mehr Entwurfsprinzipien können Sie im Compiler erzwingen.

Wir können den Benutzern auch die Arbeit erleichtern. Oft arbeiten sie nur mit regulären Sammlungen oder Fallklassen, und wir konvertieren sie automatisch in JSON oder was auch immer, je nach Bedarf für den Netzwerktransport.

Starkes Tippen hilft auch dabei, zwischen Dingen wie nicht bereinigten und bereinigten Eingaben zu unterscheiden, was zur Sicherheit beitragen kann.

Durch starkes Tippen können sich Ihre Tests auch stärker auf Ihr tatsächliches Verhalten konzentrieren, anstatt eine Reihe von Tests zu benötigen, die nur Ihre Typen testen. Dies macht das Testen viel angenehmer, fokussierter und daher effektiver.

Der Hauptnachteil ist die Unkenntnis der Sprache und des Sprachparadigmas, und das kann mit der Zeit korrigiert werden. Ansonsten haben wir festgestellt, dass sich die Mühe gelohnt hat.

10
Karl Bielefeldt

Obwohl dies keine direkte Antwort ist (da ich in haskell noch nicht an +30.000 LOC-Codebasen gearbeitet habe :( ..), bitte ich Sie, https://www.fpcomplete.com/business/resources) zu überprüfen/case-study / enthält viele Fallstudien zu Haskell in aktuellen Branchenumgebungen.

Ein weiterer guter Artikel ist IMVU, der ihre Erfahrungen bei der Umstellung auf haskell beschreibt - http://engineering.imvu.com/2014/03/24/what-its-like-to-use-haskell/ .

Aus persönlicher Erfahrung in größeren Anwendungen hilft Ihnen das Typsystem sehr sehr, insbesondere wenn Sie versuchen, so viel wie möglich in Typen zu codieren. Die wahre Kraft ist wirklich offensichtlich, wenn es darum geht, Dinge umzugestalten - was bedeutet, dass Wartung und dergleichen zu einer viel weniger besorgniserregenden Aufgabe werden.

Ich werde ein paar Links zu Ressourcen ablegen, die ich empfehle, da Sie ziemlich viele Fragen gleichzeitig stellen:

Abschließend wird gesagt, dass der Umgang mit der Außenwelt auf verschiedene Weise erfolgt. Es gibt Bibliotheken, die sicherstellen, dass die Dinge an Ihrem Ende typsicher sind, wie Aeson für JSON, Esqueleto für SQL und viele mehr.

8
Tehnix

Was ich gesehen habe:

Ich habe ein paar große Ruby Webanwendungen (Rails)), eine große Haskell-Webanwendung und mehrere kleinere bearbeitet. Mit dieser Erfahrung muss ich sagen, dass die Arbeit an den Haskell-Anwendungen viel ist einfacher als in Rails in Bezug auf Wartung und niedrigere Lernkurve. Ich bin der Meinung, dass diese Vorteile sowohl auf das Haskell-Typsystem als auch auf den funktionalen Programmierstil zurückzuführen sind. Im Gegensatz zu vielen anderen glaube ich jedoch Dass der "statische" Teil des Typsystems nur eine große Annehmlichkeit ist, da bei der Verwendung dynamischer Verträge immer noch Vorteile bestehen.

Was ich glaube

Es gibt ein schönes Paket namens Contracts Ruby , das einige der Hauptfunktionen bietet, die meiner Meinung nach Haskell-Projekten helfen, bessere Wartungseigenschaften zu erzielen. Verträge Ruby führt seine Überprüfungen zur Laufzeit durch, daher ist es am besten, wenn es mit einer hohen Testkonvergenz gepaart wird, bietet jedoch weiterhin die gleiche Inline-Dokumentation und den gleichen Ausdruck von Absicht und Bedeutung wie die Verwendung von Typanmerkungen in Sprachen wie Haskell.

Anwser zur Frage

Um die oben gestellten Fragen zu beantworten, gibt es viele Stellen, an denen man sich mit Haskell und anderen Sprachen mit fortschrittlichen Typsystemen vertraut machen kann. Und um ganz ehrlich zu sein, obwohl diese Dokumentationsquellen für sich genommen ausgezeichnet sind, scheinen sie alle im Vergleich zu der Fülle an Dokumentationen und praktischen Ratschlägen in Ruby, Python, Java) ein wenig überwältigend zu sein = und andere solche Sprachen. Auf jeden Fall wird Real World Haskell alt, ist aber immer noch eine gute Ressource.

Kategorietheorie

Wenn Sie sich für Haskell entscheiden, werden Sie auf große Mengen an Literatur stoßen, die sich mit Kategorietheorie befasst. IMHO Kategorietheorie ist nützlich, aber nicht notwendig. Angesichts der Verbreitung in der Haskell-Community ist es leicht, die Vor- und Nachteile von Typen mit Gefühlen über die Praktikabilität der Kategorietheorie zu verbinden. Es ist hilfreich, sich daran zu erinnern, dass es sich um zwei verschiedene Dinge handelt, dh, Implementierungen, die von der Kategorietheorie geleitet werden, können sowohl in dynamisch typisierten als auch in statischen Sprachen durchgeführt werden (modulo die Vorteile, die das Typensystem bietet). Fortgeschrittene Typsysteme sind im Allgemeinen nicht an die Kategorietheorie gebunden, und die Kategorietheorie ist nicht an Typsysteme gebunden.

Mehr zu Typen

Wenn Sie mehr über das Programmieren mit Typen und die darin enthaltenen Techniken lernen (was ziemlich schnell geschieht, weil es Spaß macht), möchten Sie mehr mit dem Typensystem ausdrücken. In diesem Fall würde ich mich mit einigen der folgenden Ressourcen befassen und gemeinsam mit mir den Werkzeuganbietern bewusst machen, dass wir Werkzeuge in Industriequalität mit diesen Funktionen wünschen, die nur in etwas verpackt sind, das eine benutzerfreundliche Oberfläche bietet (wie Contracts Ruby):

3
Eric

Erstens habe ich das Gefühl, dass die Antworten zwischen schwach und stark typisiert und statisch und dynamisch typisiert sind. Die Verknüpfung des bereitgestellten OP macht die Unterscheidung deutlich:

Ein starkes Typsystem ist ein Typsystem mit einer Einschränkung der Kompilierungszeit oder einer Laufzeitfunktion, die Sie attraktiv finden.

Ein schwaches Typsystem ist ein Typsystem, dem diese Einschränkung oder Funktion fehlt.

Zum Beispiel werden C, C++ und Java statisch typisiert, da Variablen zur Kompilierungszeit typisiert werden. C und C++ können jedoch als schwach typisiert betrachtet werden, da die Sprache die Umgehung von Einschränkungen mit void * Zeiger und Casts . Mehr zu diesem Thema.

Bei dieser Unterscheidung kann eine starke Typisierung nur besser sein. Je früher das Scheitern, desto besser.

Beim Schreiben großer Programme denke ich jedoch nicht, dass das Typensystem eine wichtige Rolle spielt. Der Linux-Kernel ist zehn Millionen LOC in C und Assembly geschrieben und wird als sehr stabiles Programm angesehen. Er ist meilenweit von meinen 200 Java -Linien entfernt, die wahrscheinlich voller Sicherheitslücken sind. Ähnlich, obwohl dynamisch typisiert " Skriptsprachen "haben einen schlechten Ruf, wenn es darum geht, große Programme zu schreiben. Es gibt gelegentlich Beweise dafür, dass sie unverdient sind (wie Python Django, über 70.000 LOC).

Meiner Meinung nach dreht sich alles um Qualitätsstandards. Die Verantwortung für die Skalierbarkeit großer Anwendungen sollte nur von den Programmierern und Architekten und ihrem Willen getragen werden, die Anwendung sauber, getestet, gut dokumentiert usw. zu machen.

2
Arthur Havlicek