)) } } }

Scala - Επιλογή Monad / Ίσως Monad

import language.higherKinds trait Monad[M[_]] { def pure[A](a: A): M[A] def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] def map[A, B](ma: M[A])(f: A => B): M[B] = flatMap(ma)(x => pure(f(x))) } object Monad { def apply[F[_]](implicit M: Monad[F]): Monad[F] = M implicit val myOptionMonad = new Monad[MyOption] { def pure[A](a: A) = MySome(a) def flatMap[A, B](ma: MyOption[A])(f: A => MyOption[B]): MyOption[B] = ma match { case MyNone => MyNone case MySome(a) => f(a) } } } sealed trait MyOption[+A] { def flatMap[B](f: A => MyOption[B]): MyOption[B] = Monad[MyOption].flatMap(this)(f) def map[B](f: A => B): MyOption[B] = Monad[MyOption].map(this)(f) } case object MyNone extends MyOption[Nothing] case class MySome[A](x: A) extends MyOption[A]

Ξεκινάμε εφαρμόζοντας ένα Monad τάξη που θα είναι η βάση για όλες τις υλοποιήσεις μας. Η ύπαρξη αυτής της τάξης είναι πολύ βολική, διότι εφαρμόζοντας μόνο δύο από τις μεθόδους της - pure και flatMap —για μια συγκεκριμένη μονάδα, θα λάβετε πολλές μεθόδους δωρεάν (τις περιορίζουμε μόνο στη μέθοδο map στα παραδείγματα μας, αλλά γενικά υπάρχουν πολλές άλλες χρήσιμες μέθοδοι, όπως sequence και traverse για εργασία με συστοιχίες Monad s).

Μπορούμε να εκφράσουμε map ως σύνθεση του pure και flatMap. Μπορείτε να δείτε από την υπογραφή flatMap $ flatMap: (T to M [U]) to (M [T] to M [U]) $ ότι είναι πολύ κοντά στο $ map: (T σε U) έως (M [T] to M [U]) $. Η διαφορά είναι το επιπλέον $ M $ στη μέση, αλλά μπορούμε να χρησιμοποιήσουμε το pure συνάρτηση για τη μετατροπή $ U $ σε $ M [U] $. Με αυτόν τον τρόπο εκφράζουμε map σε όρους flatMap και pure.

Αυτό λειτουργεί καλά για τη Scala, επειδή διαθέτει προηγμένο σύστημα τύπου. Λειτουργεί επίσης καλά για τους JS, Python και Ruby, επειδή είναι δυναμικά δακτυλογραφημένοι. Δυστυχώς, δεν λειτουργεί για το Swift, επειδή έχει στατική πληκτρολόγηση και δεν διαθέτει προηγμένες λειτουργίες τύπου ανώτεροι τύποι , έτσι για το Swift θα πρέπει να εφαρμόσουμε map για κάθε μονάδα.

Σημειώστε επίσης ότι το Option monad είναι ήδη ένα στην πραγματικότητα πρότυπο για γλώσσες όπως το Swift και το Scala, οπότε χρησιμοποιούμε ελαφρώς διαφορετικά ονόματα για τις υλοποιήσεις μας.

Τώρα που έχουμε μια βάση Monad μαθήματα, ας δούμε τις εφαρμογές monad Option. Όπως αναφέρθηκε προηγουμένως, η βασική ιδέα είναι ότι η Επιλογή διατηρεί κάποια τιμή (ονομάζεται Some) ή ή δεν έχει καθόλου τιμή (None).

Το pure Η μέθοδος απλώς προάγει μια τιμή σε Some, ενώ η flatMap Η μέθοδος ελέγχει την τρέχουσα τιμή του Option - αν είναι None τότε επιστρέφει None και αν είναι Some με μια υποκείμενη τιμή, εξάγει την υποκείμενη τιμή, ισχύει f() σε αυτό και επιστρέφει ένα αποτέλεσμα.

Λάβετε υπόψη ότι απλώς χρησιμοποιώντας αυτές τις δύο λειτουργίες και (Το πρόβλημα θα μπορούσε ενδεχομένως να προκύψουν κατά την εφαρμογή του map μέθοδος, αλλά αυτό είναι μόνο μερικές γραμμές στον κώδικά μας που ελέγχουμε μία φορά. Μετά από αυτό, απλώς χρησιμοποιούμε την εφαρμογή monad Option σε ολόκληρο τον κώδικα σε χιλιάδες μέρη και δεν χρειάζεται να φοβόμαστε καθόλου την εξαίρεση του δείκτη.

The Either Monad

Ας βουτήξουμε στη δεύτερη μονάδα: Είτε. Αυτό είναι βασικά το ίδιο με το Option monad, αλλά με το flatMap ονομάζεται Some και Right ονομάζεται None. Αλλά αυτή τη φορά, Left επιτρέπεται επίσης να έχει μια υποκείμενη τιμή.

Αυτό το χρειαζόμαστε γιατί είναι πολύ βολικό να εκφράζουμε μια εξαίρεση. Εάν προέκυψε εξαίρεση, τότε η τιμή του Left θα είναι Either. Το Left(Exception) Η συνάρτηση δεν προχωρά αν η τιμή είναι flatMap, η οποία επαναλαμβάνει τη σημασιολογία των εξαιρέσεων ρίψης: Εάν συνέβη μια εξαίρεση, σταματάμε την περαιτέρω εκτέλεση.

JavaScript — Είτε Monad

Left

Python — Είτε Monad

import Monad from './monad'; export class Either extends Monad { // pure :: a -> Either a pure = (value) => { return new Right(value) } // flatMap :: # Either a -> (a -> Either b) -> Either b flatMap = f => this.isLeft() ? this : f(this.value) isLeft = () => this.constructor.name === 'Left' } export class Left extends Either { constructor(value) { super(); this.value = value; } toString() { return `Left(${this.value})` } } export class Right extends Either { constructor(value) { super(); this.value = value; } toString() { return `Right(${this.value})` } } // attempt :: (() -> a) -> M a Either.attempt = f => { try { return new Right(f()) } catch(e) { return new Left(e) } } Either.pure = (new Left(null)).pure

Ruby — Είτε Monad

from monad import Monad class Either(Monad): # pure :: a -> Either a @staticmethod def pure(value): return Right(value) # flat_map :: # Either a -> (a -> Either b) -> Either b def flat_map(self, f): if self.is_left: return self else: return f(self.value) class Left(Either): def __init__(self, value): self.value = value self.is_left = True class Right(Either): def __init__(self, value): self.value = value self.is_left = False

Swift — Είτε Monad

require_relative './monad' class Either Either a def self.pure(value) Right.new(value) end # pure :: a -> Either a def pure(value) self.class.pure(value) end # flat_map :: # Either a -> (a -> Either b) -> Either b def flat_map(f) if is_left self else f.call(value) end end end class Left

Σκάλα - Είτε Monad

import Foundation enum Either { case Left(A) case Right(B) static func pure(_ value: C) -> Either { return Either.Right(value) } func flatMap(_ f: (B) -> Either) -> Either { switch self { case .Left(let x): return Either.Left(x) case .Right(let x): return f(x) } } func map(f: (B) -> C) -> Either { return self.flatMap { Either.pure(f(

Επιλογή / Ίσως, Είτε και Μελλοντικά Monads σε JavaScript, Python, Ruby, Swift και Scala



Αυτό το σεμινάριο monad παρέχει μια σύντομη επεξήγηση των monads και δείχνει πώς να εφαρμόσετε τα πιο χρήσιμα σε πέντε διαφορετικές γλώσσες προγραμματισμού - αν ψάχνετε για monads στο JavaScript , μονάδες στο Πύθων , μονάδες στο Ρουμπίνι , μονάδες στο Ταχύς , ή / και μονάδες σε Σκάλα ή για να συγκρίνετε τυχόν υλοποιήσεις, διαβάζετε το σωστό άρθρο!

Χρησιμοποιώντας αυτές τις μονάδες, θα απαλλαγείτε από μια σειρά σφαλμάτων, όπως εξαιρέσεις από μηδενικό δείκτη, εξαιρέσεις χωρίς χειρισμό και συνθήκες αγώνα.



Αυτό καλύπτω παρακάτω:



  • Εισαγωγή στη θεωρία της κατηγορίας
  • Ο ορισμός της μονάδας
  • Εφαρμογές της επιλογής ('Ίσως') monad, Είτε monad και Future monad, καθώς και ένα δείγμα προγράμματος που τις αξιοποιεί, σε JavaScript, Python, Ruby, Swift και Scala

Ας αρχίσουμε! Η πρώτη μας στάση είναι η θεωρία κατηγορίας, η οποία είναι η βάση για τους μονάδες.



Εισαγωγή στη θεωρία της κατηγορίας

Η θεωρία κατηγορίας είναι ένα μαθηματικό πεδίο που αναπτύχθηκε ενεργά στα μέσα του 20ού αιώνα. Τώρα είναι η βάση πολλών ιδεών λειτουργικού προγραμματισμού, συμπεριλαμβανομένου του monad. Ας ρίξουμε μια γρήγορη ματιά σε ορισμένες έννοιες θεωρίας κατηγορίας, προσαρμοσμένες για την ορολογία ανάπτυξης λογισμικού.

Υπάρχουν λοιπόν τρεις βασικές έννοιες που ορίζουν ένα κατηγορία:



  1. Τύπος είναι ακριβώς όπως το βλέπουμε σε στατικά δακτυλογραφημένες γλώσσες. Παραδείγματα: Int, String, Dog, Cat κ.λπ.
  2. Λειτουργίες συνδέστε δύο τύπους. Επομένως, μπορούν να αναπαρασταθούν ως βέλος από έναν τύπο σε έναν άλλο τύπο ή για τον εαυτό τους. Η συνάρτηση $ f $ από τον τύπο $ T $ στον τύπο $ U $ μπορεί να δηλωθεί ως $ f: T έως U $. Μπορείτε να το θεωρήσετε ως μια λειτουργία γλώσσας προγραμματισμού που παίρνει ένα όρισμα τύπου $ T $ και επιστρέφει μια τιμή τύπου $ U $.
  3. Σύνθεση είναι μια λειτουργία, που υποδηλώνεται από τον τελεστή $ cdot $, που δημιουργεί νέες συναρτήσεις από υπάρχουσες. Σε μια κατηγορία, είναι πάντα εγγυημένη για οποιεσδήποτε συναρτήσεις $ f: T έως U $ και $ g: U to V $ υπάρχει μια μοναδική συνάρτηση $ h: T έως V $. Αυτή η συνάρτηση δηλώνεται ως $ f cdot g $. Η λειτουργία χαρτογραφεί αποτελεσματικά ένα ζευγάρι συναρτήσεων σε μια άλλη λειτουργία. Σε γλώσσες προγραμματισμού, αυτή η λειτουργία είναι, φυσικά, πάντα δυνατή. Για παράδειγμα, εάν έχετε μια συνάρτηση που επιστρέφει ένα μήκος μιας συμβολοσειράς - $ strlen: String to Int $ —και μια συνάρτηση που λέει εάν ο αριθμός είναι ομαλός - $ even: Int to Boolean $ — τότε μπορείτε να δημιουργήσετε ένα συνάρτηση $ even { _} strlen: String to Boolean $ που λέει εάν το μήκος του String είναι ομοιόμορφο. Σε αυτήν την περίπτωση $ even { _} strlen = even cdot strlen $. Η σύνθεση συνεπάγεται δύο χαρακτηριστικά:
    1. Συσχετικότητα: $ f cdot g cdot h = (f cdot g) cdot h = f cdot (g cdot h) $
    2. Η ύπαρξη μιας συνάρτησης ταυτότητας: $ forall T: υπάρχει f: T to T $, ή στα απλά αγγλικά, για κάθε τύπο $ T $ υπάρχει μια συνάρτηση που χαρτογραφεί $ T $ στον εαυτό της.

Ας ρίξουμε μια ματιά σε μια απλή κατηγορία.

Μια απλή κατηγορία που περιλαμβάνει String, Int και Double και ορισμένες λειτουργίες μεταξύ τους.



Σημείωση: Υποθέτουμε ότι Int, String και όλοι οι άλλοι τύποι εδώ είναι εγγυημένοι ότι δεν είναι μηδενικοί, δηλαδή η μηδενική τιμή δεν υπάρχει.

Πλευρική σημείωση 2: Αυτό είναι στην πραγματικότητα μόνο μέρος μιας κατηγορίας, αλλά αυτό είναι το μόνο που θέλουμε για τη συζήτησή μας, επειδή έχει όλα τα βασικά μέρη που χρειαζόμαστε και το διάγραμμα είναι λιγότερο γεμάτο με αυτόν τον τρόπο. Η πραγματική κατηγορία θα είχε επίσης όλες τις συνθέσεις όπως $ roundToString: Double to String = intToString cdot round $, για να ικανοποιήσει τον όρο σύνθεσης των κατηγοριών.



Μπορεί να παρατηρήσετε ότι οι λειτουργίες αυτής της κατηγορίας είναι εξαιρετικά απλές. Στην πραγματικότητα, είναι σχεδόν αδύνατο να υπάρχει σφάλμα σε αυτές τις λειτουργίες. Δεν υπάρχουν μηδενικά, δεν υπάρχουν εξαιρέσεις, μόνο η αριθμητική και η εργασία με τη μνήμη. Επομένως, το μόνο κακό που μπορεί να συμβεί είναι ο επεξεργαστής ή η αποτυχία μνήμης - στην περίπτωση αυτή πρέπει να διακόψετε το πρόγραμμα ούτως ή άλλως - αλλά αυτό συμβαίνει πολύ σπάνια.

Δεν θα ήταν ωραίο εάν όλος ο κώδικάς μας λειτουργούσε σε αυτό το επίπεδο σταθερότητας; Απολύτως! Αλλά τι γίνεται με το I / O, για παράδειγμα; Σίγουρα δεν μπορούμε να ζήσουμε χωρίς αυτό. Εδώ έρχονται οι λύσεις monad για τη διάσωση: Απομονώνονται όλες οι ασταθείς λειτουργίες σε πολύ μικρά και πολύ καλά ελεγμένα κομμάτια κώδικα - τότε μπορείτε να χρησιμοποιήσετε σταθερούς υπολογισμούς σε ολόκληρη την εφαρμογή σας!



Μπείτε στο Monads

Ας καλέσουμε ασταθή συμπεριφορά όπως I / O a παρενέργεια . Τώρα θέλουμε να είμαστε σε θέση να συνεργαστούμε με όλες τις λειτουργίες που ορίσαμε προηγουμένως, όπως length και τύπους όπως String με σταθερό τρόπο παρουσία αυτού παρενέργεια .

Ας ξεκινήσουμε λοιπόν με μια κενή κατηγορία $ M [A] $ και ας το κάνουμε σε μια κατηγορία που θα έχει τιμές με έναν συγκεκριμένο τύπο παρενέργειας και επίσης τιμές χωρίς παρενέργειες. Ας υποθέσουμε ότι έχουμε ορίσει αυτήν την κατηγορία και είναι κενή. Αυτήν τη στιγμή δεν υπάρχει τίποτα χρήσιμο που μπορούμε να κάνουμε με αυτό, οπότε για να το κάνουμε χρήσιμο, θα ακολουθήσουμε αυτά τα τρία βήματα:



  1. Συμπληρώστε το με τιμές των τύπων από την κατηγορία $ A $, όπως String, Int, Double κ.λπ. (πράσινα κουτιά στο παρακάτω διάγραμμα)
  2. Μόλις έχουμε αυτές τις τιμές, δεν μπορούμε ακόμα να κάνουμε τίποτα σημαντικό μαζί τους, οπότε χρειαζόμαστε έναν τρόπο να πάρουμε κάθε συνάρτηση $ f: T έως U $ από $ A $ και να δημιουργήσουμε μια συνάρτηση $ g: M [T] to M [U] $ (μπλε βέλη στο παρακάτω διάγραμμα). Μόλις έχουμε αυτές τις λειτουργίες, μπορούμε να κάνουμε τα πάντα με τις τιμές στην κατηγορία $ M [A] $ που μπορούσαμε να κάνουμε στην κατηγορία $ A $.
  3. Τώρα που έχουμε μια ολοκαίνουργια κατηγορία $ M [A] $, εμφανίζεται μια νέα κατηγορία συναρτήσεων με την υπογραφή $ h: T to M [U] $ (κόκκινα βέλη στο παρακάτω διάγραμμα). Εμφανίζονται ως αποτέλεσμα της προώθησης τιμών στο πρώτο βήμα ως μέρος της βάσης κώδικα μας, δηλαδή, τις γράφουμε όπως απαιτείται. Αυτά είναι τα κύρια πράγματα που θα διαφοροποιήσουν την εργασία με $ M [A] $ σε σχέση με την εργασία με $ A $. Το τελευταίο βήμα θα είναι οι λειτουργίες αυτές να λειτουργούν καλά σε τύπους σε $ M [A] $, δηλαδή να μπορείτε να αντλήσετε τη συνάρτηση $ m: M [T] to M [U] $ από $ h: T σε M [U] $

Δημιουργία νέας κατηγορίας: Κατηγορίες Α και Μ [Α], συν ένα κόκκινο βέλος από το Α

Ας ξεκινήσουμε, ορίζοντας δύο τρόπους προώθησης τιμών τύπων $ A $ σε τιμές τύπων $ M [A] $: μία λειτουργία χωρίς παρενέργειες και μία με παρενέργειες.

  1. Το πρώτο ονομάζεται $ pure $ και ορίζεται για κάθε τιμή μιας σταθερής κατηγορίας: $ καθαρά: T έως M [T] $. Οι προκύπτουσες τιμές $ M [T] $ δεν θα έχουν παρενέργειες, επομένως αυτή η συνάρτηση ονομάζεται $ pure $. Π.χ., για ένα monad I / O, το $ خالص $ θα επιστρέψει κάποια τιμή αμέσως χωρίς πιθανότητα αποτυχίας.
  2. Το δεύτερο ονομάζεται $ buildor $ και, σε αντίθεση με το $ pure $, επιστρέφει $ M [T] $ με κάποιες παρενέργειες. Ένα παράδειγμα ενός τέτοιου δομικού $ $ για ένα ασύρματο μονόδρομο I / O θα μπορούσε να είναι μια συνάρτηση που ανακτά ορισμένα δεδομένα από τον Ιστό και τα επιστρέφει ως String. Η τιμή που επιστρέφεται από το $ buildor $ θα έχει τύπο $ M [String] $ σε αυτήν την περίπτωση.

Τώρα που έχουμε δύο τρόπους προώθησης τιμών σε $ M [A] $, εξαρτάται από εσάς ως προγραμματιστής να επιλέξετε ποια λειτουργία θα χρησιμοποιήσετε, ανάλογα με τους στόχους του προγράμματος. Ας δούμε ένα παράδειγμα εδώ: Θέλετε να ανακτήσετε μια σελίδα HTML όπως https://www.toptal.com/javascript/option-maybe-either-future-monads-js και για αυτό κάνετε μια συνάρτηση $ fetch $. Εφόσον κάτι μπορεί να πάει στραβά κατά τη λήψη του - σκεφτείτε τις αποτυχίες δικτύου κ.λπ. - θα χρησιμοποιήσετε το $ M [String] $ ως τύπο επιστροφής αυτής της λειτουργίας. Έτσι θα μοιάζει με $ fetch: String to M [String] $ και κάπου στο σώμα λειτουργίας εκεί θα χρησιμοποιήσουμε $ buildor $ για $ M $.

Ας υποθέσουμε ότι κάνουμε μια ψεύτικη συνάρτηση για δοκιμή: $ fetchMock: String to M [String] $. Εξακολουθεί να έχει την ίδια υπογραφή, αλλά αυτή τη φορά εισάγουμε απλώς τη σελίδα HTML που προκύπτει στο σώμα του $ fetchMock $ χωρίς να κάνουμε ασταθείς λειτουργίες δικτύου. Έτσι, σε αυτήν την περίπτωση, χρησιμοποιούμε μόνο $ خالص $ στην εφαρμογή του $ fetchMock $.

Ως επόμενο βήμα, χρειαζόμαστε μια συνάρτηση που προωθεί με ασφάλεια οποιαδήποτε αυθαίρετη συνάρτηση $ f $ από την κατηγορία $ A $ έως $ M [A] $ (μπλε βέλη σε ένα διάγραμμα). Αυτή η συνάρτηση ονομάζεται $ map: (T to U) to (M [T] to M [U]) $.

Τώρα έχουμε μια κατηγορία (η οποία μπορεί να έχει παρενέργειες αν χρησιμοποιούμε $ buildor $), η οποία έχει επίσης όλες τις συναρτήσεις από τη σταθερή κατηγορία, που σημαίνει ότι είναι σταθερές και σε $ M [A] $. Ίσως παρατηρήσετε ότι παρουσιάσαμε ρητά μια άλλη κατηγορία συναρτήσεων όπως $ f: T to M [U] $. Π.χ., $ خالص $ και $ $ κατασκευαστής είναι παραδείγματα τέτοιων συναρτήσεων για $ U = T $, αλλά προφανώς θα μπορούσαν να υπάρχουν περισσότερα, σαν να χρησιμοποιούσαμε $ خالص $ και μετά $ ​​$ $. Έτσι, γενικά, χρειαζόμαστε έναν τρόπο αντιμετώπισης αυθαίρετων συναρτήσεων με τη μορφή $ f: T to M [U] $.

Αν θέλουμε να δημιουργήσουμε μια νέα συνάρτηση με βάση $ f $ που θα μπορούσε να εφαρμοστεί σε $ M [T] $, θα μπορούσαμε να προσπαθήσουμε να χρησιμοποιήσουμε $ map $. Αλλά αυτό θα μας φέρει σε λειτουργία $ g: M [T] to M [M [U]] $, κάτι που δεν είναι καλό αφού δεν θέλουμε να έχουμε μια ακόμη κατηγορία $ M [M [A]] $. Για την αντιμετώπιση αυτού του προβλήματος, παρουσιάζουμε μια τελευταία συνάρτηση: $ flatMap: (T to M [U]) to (M [T] to M [U]) $.

Αλλά γιατί θα θέλαμε να το κάνουμε αυτό; Ας υποθέσουμε ότι ακολουθούμε το βήμα 2, δηλαδή έχουμε $ καθαρά $, $ κατασκευαστή $ και $ map $. Ας υποθέσουμε ότι θέλουμε να τραβήξουμε μια σελίδα HTML από το toptal.com και, στη συνέχεια, να σαρώσουμε όλες τις διευθύνσεις URL εκεί και να τις πάρουμε. Θα έκανα μια συνάρτηση $ fetch: String to M [String] $ που παίρνει μόνο μία διεύθυνση URL και επιστρέφει μια σελίδα HTML.

Στη συνέχεια, θα χρησιμοποιούσα αυτήν τη λειτουργία σε μια διεύθυνση URL και θα λάβω μια σελίδα από το toptal.com, η οποία είναι $ x: M [String] $. Τώρα, κάνω κάποια μετατροπή στα $ x $ και τελικά φτάνω σε κάποια διεύθυνση URL $ u: M [String] $. Θέλω να εφαρμόσω τη συνάρτηση $ fetch $ σε αυτήν, αλλά δεν μπορώ, επειδή χρειάζεται τύπος $ String $, όχι $ M [String] $. Γι 'αυτό χρειαζόμαστε $ flatMap $ για να μετατρέψουμε το $ fetch: String to M [String] $ σε $ m_fetch: M [String] to M [String] $.

Τώρα που έχουμε ολοκληρώσει και τα τρία βήματα, μπορούμε πραγματικά να συνθέσουμε τυχόν μετασχηματισμούς αξίας που χρειαζόμαστε. Για παράδειγμα, εάν έχετε τιμή $ x $ τύπου $ M [T] $ και $ f: T to U $, μπορείτε να χρησιμοποιήσετε το $ map $ για να εφαρμόσετε $ f $ στην αξία $ x $ και να λάβετε αξία $ y $ τύπου $ M [U] $. Με αυτόν τον τρόπο κάθε μετασχηματισμός των τιμών μπορεί να γίνει με 100% χωρίς σφάλματα, αρκεί οι εφαρμογές $ $ $, $ buildor, $ map $ και $ flatMap $ να είναι χωρίς σφάλματα.

Επομένως, αντί να αντιμετωπίζετε κάποια δυσάρεστα εφέ κάθε φορά που τα συναντάτε στη βάση κώδικα, απλά πρέπει να βεβαιωθείτε ότι μόνο αυτές οι τέσσερις λειτουργίες εφαρμόζονται σωστά. Στο τέλος του προγράμματος, θα λάβετε μόνο ένα $ M [X] $ όπου μπορείτε να ξετυλίξετε με ασφάλεια την τιμή $ X $ και να χειριστείτε όλες τις περιπτώσεις σφαλμάτων.

Αυτό είναι το monad: ένα πράγμα που εφαρμόζει $ καθαρά $, $ map $ και $ flatMap $. (Στην πραγματικότητα $ map $ μπορεί να προέρχεται από $ pure $ και $ flatMap $, αλλά είναι πολύ χρήσιμη και διαδεδομένη λειτουργία, οπότε δεν το παραλείψα από τον ορισμό.)

Η επιλογή Monad, δηλαδή το Ίσως Monad

Εντάξει, ας δούμε την πρακτική εφαρμογή και τη χρήση των μονάδων. Το πρώτο πραγματικά χρήσιμο monad είναι το Option monad. Αν προέρχεστε από κλασικές γλώσσες προγραμματισμού, πιθανότατα αντιμετωπίσατε πολλά σφάλματα λόγω του περίφημου σφάλματος μηδενικού δείκτη. Ο Tony Hoare, ο εφευρέτης του null, αποκαλεί αυτήν την εφεύρεση «Το λάθος των δισεκατομμυρίων δολαρίων»:

Αυτό έχει οδηγήσει σε αναρίθμητα σφάλματα, ευπάθειες και σφάλματα του συστήματος, τα οποία πιθανώς προκάλεσαν ένα δισεκατομμύριο δολάρια πόνο και ζημιά τα τελευταία σαράντα χρόνια.

Ας προσπαθήσουμε λοιπόν να το βελτιώσουμε. Το monad Option είτε έχει κάποια μη μηδενική τιμή, είτε καμία τιμή. Πολύ παρόμοια με μια τιμή null, αλλά έχοντας αυτό το monad, μπορούμε να χρησιμοποιήσουμε με ασφάλεια τις καλά καθορισμένες λειτουργίες μας χωρίς να φοβόμαστε την εξαίρεση του null pointer. Ας ρίξουμε μια ματιά στις εφαρμογές σε διαφορετικές γλώσσες:

JavaScript — Επιλογή Monad / Ίσως Monad

class Monad { // pure :: a -> M a pure = () => { throw 'pure method needs to be implemented' } // flatMap :: # M a -> (a -> M b) -> M b flatMap = (x) => { throw 'flatMap method needs to be implemented' } // map :: # M a -> (a -> b) -> M b map = f => this.flatMap(x => new this.pure(f(x))) } export class Option extends Monad { // pure :: a -> Option a pure = (value) => { if ((value === null) || (value === undefined)) { return none; } return new Some(value) } // flatMap :: # Option a -> (a -> Option b) -> Option b flatMap = f => this.constructor.name === 'None' ? none : f(this.value) // equals :: # M a -> M a -> boolean equals = (x) => this.toString() === x.toString() } class None extends Option { toString() { return 'None'; } } // Cached None class value export const none = new None() Option.pure = none.pure export class Some extends Option { constructor(value) { super(); this.value = value; } toString() { return `Some(${this.value})` } }

Python — Επιλογή Monad / Ίσως Monad

class Monad: # pure :: a -> M a @staticmethod def pure(x): raise Exception('pure method needs to be implemented') # flat_map :: # M a -> (a -> M b) -> M b def flat_map(self, f): raise Exception('flat_map method needs to be implemented') # map :: # M a -> (a -> b) -> M b def map(self, f): return self.flat_map(lambda x: self.pure(f(x))) class Option(Monad): # pure :: a -> Option a @staticmethod def pure(x): return Some(x) # flat_map :: # Option a -> (a -> Option b) -> Option b def flat_map(self, f): if self.defined: return f(self.value) else: return nil class Some(Option): def __init__(self, value): self.value = value self.defined = True class Nil(Option): def __init__(self): self.value = None self.defined = False nil = Nil()

Ruby — Επιλογή Monad / Ίσως Monad

class Monad # pure :: a -> M a def self.pure(x) raise StandardError('pure method needs to be implemented') end # pure :: a -> M a def pure(x) self.class.pure(x) end def flat_map(f) raise StandardError('flat_map method needs to be implemented') end # map :: # M a -> (a -> b) -> M b def map(f) flat_map(-> (x) { pure(f.call(x)) }) end end class Option Option a def self.pure(x) Some.new(x) end # pure :: a -> Option a def pure(x) Some.new(x) end # flat_map :: # Option a -> (a -> Option b) -> Option b def flat_map(f) if defined f.call(value) else $none end end end class Some