Monaden in anderen Programmiersprachen Themen Informatik-Seminar SS 2013: Programmiersprachen und Sprachsysteme Bei: Prof. Dr. Schmidt, FH Wedel inf9500 Sebastian Philipp Überblick ● Motivation ● Monaden in Haskell ● Monaden in C# ● Monaden in Ruby ● Monaden in Javascript Motivation Wieso dieses Seminarthema? Asynchrone Algorithmen ● ● ● ● Concurrency ohne Threads Funktionen rufen Callbacks auf Beispiel Node.js Callbacks sollen ○ Nie warten ○ Nie blockieren ○ Schnell zurückkehren ● Problem: verschachtelte Callback-Handler Asynchrone NetzwerkRequests mit Qt ● Qt ist ein Framework für C++ ● Netzwerk API ist asynchron ○ Callback wird aufgerufen, sobald Request beendet wurde. ○ Danach neuen Request starten Problem Algorithmus ○ ○ ○ ○ ○ Sehr viele unterschiedliche Requests Rekursion HTTP-Redirekts Sessions Fehlerbehandlung Traditionelle Implementierung scheidet aus. Lösung: Monaden? ● Asynchron: ○ Ein Thread ○ Progress, Abort ● Synchron ○ Lokale Variablen ○ Kontrollstrukturen (Funktionen, Rekursionen) Motivation: MaybeFunktionen Gegeben: Library-Funktionen, die Error-Codes oder Bool als returnValue liefern if (!funcA(..)) return false; if (!funcB(...)) return false; Trennen von Inhalt und Fehlerbehandlung? Monaden in Haskell Monaden sind parametrisierbare Typen Monaden implementieren dieses Interface: unit :: a -> m a bind :: m a -> (a -> m b) -> m b Monadengesetze (unit x) `bind` f == f x x `bind` unit == x (x `bind` f) `bind` g == x `bind` (\ y -> f y `bind` g) ● Neutralität von unit ● Assoziativität von bind ● Infixnotation in anderen Sprachen? Maybe-Monade Die einfachste Monade data Maybe a = Nothing | Just a unit :: a -> Maybe a unit x = Just x bind :: Maybe a -> (a -> Maybe b) -> Maybe b bind Nothing f = Nothing bind (Just x) f = f x Do-Notation ● func1 und func2 liefern beide möglicherweise ein Ergebnis f :: a -> Maybe b f x = do y <- func1 x z <- func2 y unit z Do-Notation ● func1 und func2 liefern beide möglicherweise ein Ergebnis f :: a -> Maybe b f x = func1 x `bind` \ y -> func2 y `bind` \ z -> unit z Monaden in Haskell ● Do-Notation: Einfache Transformation ● In Haskell sind Lambda-Ausdrücke extrem einfach. ● Monaden in Sprachen ohne Lambda? Monaden in C# LINQ LINQ LINQ ist eine Syntax in Anlehnung an SQL Ziel: Zugriff auf Daten int[] func(int[] numbers) { return from n in numbers where n > 5 select n + 1; } LINQ Bindings für Datenbanken, Container, XML und Arrays XElement books = XElement.Parse( @"<books> <book> <title>Pro LINQ: Language Integr. Query in C# 2010</title> <author>Joe Rattz</author> </book> <book> <title>Pro .NET 4.0 Parallel Programming in C#</title> <author>Adam Freeman</author> </book> </books>"); LINQ var titles = from book in books.Elements("book") where (string)book.Element("author") == "Joe Rattz" select book.Element("title"); ● Schlüsselwörter: from, where, select, group, orderby, ... ● Jede LINQ-Abfrage endet mit select. Ist das ein Problem? LINQ int[] linq(int[] numbers) { return from n in numbers where n > 5 select n + 1; } wird transformiert in int[] linq(int[] numbers) { return numbers .Where(n => n > 5) .Select(n => n + 1) } LINQ und Monaden Mehrere "from .. in" - Anweisungen werden mit SelectMany zusammengebunden. public static M<B> SelectMany<A, B>( this M<A> source, Func<A, M<B>> selector ) Das ist die bind Funktion aus Haskell. LINQ und Monaden public Maybe<int> DoSomeDivision(int denominator) { return from a in 12.Div(denominator) from b in a.Div(2) select b; } ● Wie können wir die Maybe-Monade mit SelectMany implementieren? Maybe Monade in C# Unser Datentyp: public interface Maybe<T> { } public class Nothing<T> : Maybe<T> { } public class Just<T> : Maybe<T> { public T Value; public Just(T value) { Value = value; } } Maybe-Monade in C# Unsere unit Funktion: public static Maybe<T> ToMaybe<T>(this T value) { return new Just<T>(value); } Nicht unbedingt nötig in C#, verbessert aber die Lesbarkeit. Maybe-Monade in C# Unsere bind-Funktion: public static Maybe<B> SelectMany<A, B>( this Maybe<A> source, Func<A, Maybe<B>> selector ) { var justA = source as Just<A>; return justA == null ? new Nothing<B>() : selector(justA.Value); } Async in C# und F# ● Async ist in C# keine Monade ○ Nicht mithilfe der Continuation Monade implementiert ○ Ähnlich wie eine Koroutine ○ await ⇔ yield ● F# : Async Workflows ○ Ist eine Monade. F# Async-Workflow let downloadAndExtractLinks url = async { let webClient = new System.Net.WebClient() let html = webClient .DownloadString(url : string) let! links = extractLinksAsync html return url, links.Count } Computational Expressions sind Monaden. F# Async Die Do-Notation in F#. Construct De-sugared Form let pat = expr in cexpr let pat = expr in cexpr let! pat = expr in cexpr b.Bind(expr, (fun pat -> cexpr)) Monaden in Ruby Ruby unterstützt mehrere Programmierparadigmen: ● Objektorientierung ○ "Infix" bind ● Funktionale Programmierung ○ Bekannte Funktionen: map, concat (flatten) Monadengesetze in Ruby m.class.unit[x].bind[f] == f[x] x.bind[m.class.unit] == x x.bind[f].bind[g] == x.bind[lambda { |y| f[y].bind[y] }] ● Gibt es Unterschiede zu den anderen Sprachen? ● Wieso eckige Klammern für die Funktionsaufrufe? Lazy Identitätsmonade class Id def initialize(lam) @v = lam end def force # :: Id a -> a @v[] end def self.unit # a -> Id a lambda { |x| Id.new(lambda { x }) } end def bind # :: Id a -> (a -> Id b) -> Id b x = self lambda { |f| f[x.force] } end end Ruby Funktionsaufruf Was Funktion Lambda func Aufruf ohne Parameter Funktionsobjekt func(x) x als Parameter Syntaxfehler func (x) x als Parameter Syntaxfehler func[x] Syntaxfehler* x als Parameter func [x] [x] als Parameter x als Parameter * hier wird func ohne Parameter ausgeführt, dann das Ergebnis als Lambda interpretiert. Lazy Identitätsmonade Anwendung der Identitätsmonade: x = Id.unit[20] .bind[lambda { |x| puts x; Id.unit[x * 2] } x.force Die 20 wird nicht ausgegeben, bis wir x.force aufrufen. Set-Monade require "set" class Set def self.unit # :: a -> Set a lambda { |x| Set.new [x] } end def bind # :: Set a -> ( a -> Set b ) -> Set b s = self lambda { |f| Set.new(self.map(&f) .map{ |x| x.map{|y|y} } .flatten(1)) } end Set-Monade ● Wieso gibt es keine Set-Monade in Haskell? ● Wofür kann sie verwendet werden? ● Was ist das Ergebnis? s = Set.new [1,2,3] s.bind[lambda { |x| Set.new [x,x+1,x+10] }] Set Monade: Mixin module Monad def map lambda { |f| bind[lambda { |x| self.class.unit[f[x]] }] } end end ● Mit diesem Mixin können wir map für alle möglichen Typen verwenden. ● Andere Funktionen sinnvoll? class Set include Monad end Monaden in Javascript Keine Monade in Javascript: ● JQuery ○ JQuery ist Method-Chainig Monaden in Javascript Monaden sind Js-Objekte mit den Funktionen function unit(value) function bind(monad, function(value)); Monadengesetze in Js unit(value).bind(f) === f(value) monad.bind(unit) === monad monad.bind(f).bind(g) === monad.bind(function (value) { return f(value).bind(g) }) Gibt es Unterschiede zu Ruby? Maybe-Monade in Js var Maybe = function(value) { this.value = value; }; Maybe.unit = function(value) { return new Maybe(value) }; Maybe-Monade in Js Vererbung mit Prototypen Maybe.prototype.bind = function(fn) { if (this.value != null) return fn(this.value); return this; }; Maybe-Monade in Js Anwendung der Maybe-Monade in Js m1 = new Maybe(1); var maybe11 = m1.bind(function(v) { return new Maybe(v + 10) }) Maybe-Monade in Js Wir können normale Funktionen in monadische Funktionen heben: Maybe.lift = function(fn) { return function(m) { return m.bind(function(x) { return Maybe.unit(fn(x)); }) }; }; Maybe-Monade in Js Anwendung von maybeAddOne var addOne = function(val) { return val + 1; }; var maybeAddOne = Maybe.lift(addOne); maybeAddOne(m1).ret() // == 2 maybeAddOne(new Maybe(undefined)) //undefined LiftM2 in Js Maybe.liftM2 = function(fn) { return function(M1, M2) { return M1.bind(function(val1) { return M2.bind(function(val2) { return Maybe.unit(fn(val1, val2)); }); }); }; }; Zusammenfassung ● Haskell ○ Keine Set-Monade? ○ Schöne Do-Notation ● C# ○ Kann Monaden ● Ruby ○ Lambdas sind nicht First-Class-Objects ○ Keine Do-Notations Lösung: Callbacks bool putFile(File f) { network.put(f); } bool onRequestFinished() { if (network.hasError()) ... swich (currentState) ... } Eigenschaften ○ ○ ○ ○ ○ Eine onRequestFinished() Methode Eine C++-Klasse Globale States Implizite Statemachine Unwartbar Lösung: Synchron bool putFile(File f) { network.put(f); network.wait(); return network.hasError(); } Probleme: ○ ○ ○ ○ ○ Extra Netzwerk-Thread Ein Request pro Thread Caching Probleme Progress Request abbrechen?