个性化阅读
专注于IT技术分析

JavaScript,Python,Ruby,Swift和Scala中的Option/Maybe,Ether和Future Monad

本文概述

本monad教程简要说明了monad, 并展示了如何在五种不同的编程语言中实现最有用的-如果你正在寻找JavaScript中的monad, Python中的monad, Ruby中的monad, Swift中的monad和/或monad。在Scala中, 或者要比较任何实现, 你正在阅读正确的文章!

使用这些monad, 你将摆脱一系列错误, 例如空指针异常, 未处理的异常和竞争条件。

这是我在下面介绍的内容:

  • 范畴论导论
  • Monad的定义
  • Option(“也许”)monad, Either monad和Future monad的实现, 以及利用它们的示例程序在JavaScript, Python, Ruby, Swift和Scala中

让我们开始吧!我们的第一站是范畴论, 这是Monad论的基础。

范畴论导论

范畴理论是在20世纪中叶积极发展的数学领域。现在, 它是包括monad在内的许多功能编程概念的基础。让我们快速浏览一些针对软件开发术语而调整的类别理论概念。

因此, 有三个定义类别的核心概念:

  1. 类型就像我们在静态类型语言中看到的那样。示例:整数, 字符串, 狗, 猫等。
  2. 函数连接两种类型。因此, 它们可以表示为从一种类型到另一种类型或它们自己的箭头。从$ T $类型到$ U $类型的函数$ f $可以表示为$ f:T \ to U $。你可以将其视为一种编程语言函数, 该函数接受$ T $类型的参数并返回$ U $类型的值。
  3. 合成是一个由$ \ cdot $运算符表示的操作, 它可以从现有功能中构建新功能。在类别中, 对于任何函数$ f:T \ to U $和$ g:U \ to V $, 始终存在唯一的函数$ h:T \ to V $。此函数表示为$ f \ cdot g $。该操作有效地将一对功能映射到另一个功能。当然, 在编程语言中, 始终可以执行此操作。例如, 如果你有一个返回字符串长度的函数— $ strlen:String \ to Int $ —和一个告诉数字是否为偶数的函数— $ even:Int \ to Boolean $ —那么你可以$ even {\ _} strlen函数:String \ to Boolean $, 它判断字符串的长度是否为偶数。在这种情况下, $ even {\ _} strlen =甚至\ cdot strlen $。组成具有两个特征:
    1. 关联性:$ f \ cdot g \ cdot h =(f \ cdot g)\ cdot h = f \ cdot(g \ cdot h)$
    2. 身份函数的存在:$ \ forall T:\ exists f:T \ to T $, 或者用简单的英语来说, 对于每种类型的$ T $, 都存在一个将$ T $映射到自身的函数。

因此, 让我们看一个简单的类别。

一个简单的类别,涉及String,Int和Double,以及其中的一些函数。

旁注:我们假设此处的Int, String和所有其他类型均保证为非null, 即null值不存在。

旁注2:实际上, 这只是类别的一部分, 但这只是我们讨论的全部内容, 因为它具有我们需要的所有基本组成部分, 并且该图的混乱程度也较小。真实类别还将具有所有组合函数, 例如$ roundToString:Double \ to String = intToString \ cdot round $, 以满足类别的composition子句。

你可能会注意到, 此类别中的功能非常简单。实际上, 在这些功能中存在错误几乎是不可能的。没有空值, 没有异常, 只有算术运算和使用内存。因此, 唯一可能发生的坏事是处理器或内存故障(在这种情况下, 你仍然需要使程序崩溃), 但是这种情况很少发生。

如果我们所有的代码都只在这种稳定性水平下工作, 那会很好吗?绝对!但是, 例如, I / O呢?我们绝对不能没有它。这就是monad解决方案的出路:它们将所有不稳定的操作隔离为非常小的且经过很好审计的代码段, 然后你就可以在整个应用程序中使用稳定的计算!

输入Monad

让我们将不稳定的行为(例如I / O)称为副作用。现在, 我们希望能够在存在这种副作用的情况下, 以稳定的方式使用我们之前定义的所有函数(例如长度和类型, 例如String)。

因此, 让我们从一个空类别$ M [A] $开始, 并使其成为一个类别, 该类别将具有带有一种特定类型的副作用的值以及没有副作用的值。假设我们已经定义了此类别, 并且该类别为空。目前, 我们对此无能为力, 因此, 为了使它有用, 我们将遵循以下三个步骤:

  1. 用类别$ A $中的类型的值填充它, 例如String, Int, Double等(下图中的绿色框)。
  2. 一旦有了这些值, 我们仍然无法对它们执行任何有意义的操作, 因此我们需要一种方法来将每个函数$ f:T \转换为$ A $中的U $并创建一个函数$ g:M [T] \ to M [U] $(下图中的蓝色箭头)。一旦有了这些功能, 我们就可以使用类别$ A [$]中的$ M [A] $类别的值来完成所有操作。
  3. 现在我们有了一个全新的$ M [A] $类别, 出现了带有签名$ h的新函数类:T \ to M [U] $(下图中的红色箭头)。它们是作为第一步在我们的代码库中提升价值的结果而出现的, 即, 我们根据需要编写它们;这些是区分$ M [A] $与区分$ A $的主要区别。最后一步将是使这些函数也能很好地在$ M [A] $中的类型上工作, 即, 能够从$ h:T \导出函数$ m:M [T] \ to M [U] $。到M [U] $
创建一个新类别:类别A和M [A],以及从A的Double到M [A]的Int的红色箭头,标记为" roundAsync"。此时,M [A]重用A的每个值和函数。

因此, 让我们开始定义两种将$ A $类型的值提升为$ M [A] $类型的值的方法:一种没有副作用的函数, 另一种没有副作用的函数。

  1. 第一个称为$ pure $, 是针对稳定类别的每个值定义的:$ pure:T \ to M [T] $。产生的$ M [T] $值不会有任何副作用, 因此此函数称为$ pure $。例如, 对于I / O monad, $ pure $将立即返回一些值, 而不会失败。
  2. 第二个称为$ constructor $, 与$ pure $不同, 它返回$ M [T] $, 但有一些副作用。异步I / O monad的此类$ constructor $的示例可能是一个函数, 该函数从Web上获取一些数据并将其作为String返回。在这种情况下, $ constructor $返回的值的类型为$ M [String] $。

现在, 我们有两种将值提升为$ M [A] $的方法, 这取决于你的程序目标, 由程序员作为你来选择使用哪个函数。让我们在这里考虑一个示例:你想要获取一个HTML页面, 例如https://www.srcmini02.com/javascript/option-maybe-two-future-monads-js, 为此, 你需要创建一个函数$ fetch $。由于在获取内容时可能会出错(请考虑网络故障等), 因此你将$ M [String] $作为此函数的返回类型。因此, 它看起来类似于$ fetch:String \ to M [String] $, 在函数体中的某处, 我们将$ constructor $用于$ M $。

现在假设我们创建了一个模拟函数来测试:$ fetchMock:字符串\至M [String] $。它仍然具有相同的签名, 但是这次我们只是将结果HTML页面注入$ fetchMock $的主体中, 而没有进行任何不稳定的网络操作。因此, 在这种情况下, 我们只需在$ fetchMock $的实现中使用$ pure $。

下一步, 我们需要一个函数, 该函数可以安全地将类别$ A $中的任意函数$ f $提升为$ M [A] $(图中的蓝色箭头)。此功能称为$ map:(T \ to U)\ to(M [T] \ to M [U])$。

现在我们有了一个类别(如果使用$ constructor $可能会有副作用), 它也具有稳定类别中的所有功能, 这意味着它们在$ M [A] $中也是稳定的。你可能会注意到, 我们显式引入了另一类函数, 例如$ f:T \ to M [U] $。例如, $ pure $和$ constructor $是$ U = T $的此类函数的示例, 但显然还有更多的功能, 例如如果我们先使用$ pure $然后使用$ map $。因此, 通常, 我们需要一种处理形式为$ 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步, 即有$ pure $, $ constructor $和$ map $。假设我们要从srcmini02.com抓取HTML页面, 然后在其中扫描所有URL并获取它们。我将创建一个函数$ fetch:String \ to M [String] $, 该函数仅获取一个URL并返回HTML页面。

然后, 我将此功能应用于网址, 并从srcmini02.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] $类型。这样, 只要$ pure $, $ constructor $, $ map $和$ flatMap $实现都是无错误的, 就可以以100%无错误的方式完成值的任何转换。

因此, 你不必确保每次在代码库中遇到讨厌的效果时, 只需确保仅正确实现这四个功能即可。在程序结束时, 你将只获得一个$ M [X] $, 可以在其中安全地解包$ X $值并处理所有错误情况。

这就是monad:实现$ pure $, $ map $和$ flatMap $的东西。 (实际上, $ map $可以从$ pure $和$ flatMap $派生而来, 但是它非常有用且功能广泛, 因此在定义中我没有忽略它。)

Option Monad, 又称Maybe Monad

好吧, 让我们深入研究monad的实际实现和用法。第一个真正有用的Monad是OptionMonad。如果你使用的是经典编程语言, 则可能会因为臭名昭著的空指针错误而遇到很多崩溃。 null的发明者Tony Hoare将此发明称为”十亿美元的错误”:

这导致了无数的错误, 漏洞和系统崩溃, 在最近四十年中可能造成十亿美元的痛苦和破坏。

因此, 我们尝试对此进行改进。 Option monad持有一些非空值, 或者没有值。与null值非常相似, 但是有了这个monad, 我们可以安全地使用定义良好的函数, 而不必担心null指针异常。让我们看一下不同语言的实现:

JavaScript-Option Monad /Maybe 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-Option Monad /Maybe 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-Option Monad /Maybe 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 < Monad

  attr_accessor :defined, :value

  # pure :: a -> 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 < Option
  def initialize(value)
    @value = value
    @defined = true
  end
end

class None < Option
  def initialize()
    @defined = false
  end
end

$none = None.new()

迅捷—Option Monad /Maybe Monad

import Foundation

enum Maybe<A> {
  case None
  case Some(A)
  
  static func pure<B>(_ value: B) -> Maybe<B> {
    return .Some(value)
  }
  
  func flatMap<B>(_ f: (A) -> Maybe<B>) -> Maybe<B> {
    switch self {
    case .None:
      return .None
    case .Some(let value):
      return f(value)
    }
  }
  
  func map<B>(f: (A) -> B) -> Maybe<B> {
    return self.flatMap { type(of: self).pure(f($0)) }
  }
}

Scala-Option Monad /Maybe 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类开始, 该类将成为我们所有monad实现的基础。拥有此类非常方便, 因为对于特定的monad仅实现其两种方法– pure和flatMap, 你将免费获得许多方法(在示例中, 我们仅将它们限制为map方法, 但通常有许多方法其他有用的方法, 例如用于处理Monad数组的序列和遍历)。

我们可以将地图表示为pure和flatMap的组成。你可以从flatMap的签名$ flatMap中看到:(T \ to M [U])\ to(M [T] \ to M [U])$, 它确实接近$ map:(T \ to U)\ to( M [T] \ to M [U])$。区别在于中间有额外的$ M $, 但是我们可以使用pure函数将$ U $转换为$ M [U] $。这样, 我们就可以用flatMap和pure来表示地图。

这对Scala很好, 因为它具有高级类型系统。它对于JS, Python和Ruby也很有效, 因为它们是动态类型的。不幸的是, 它不适用于Swift, 因为它是静态类型的, 并且不具有高级类型的功能(例如类型较高的类型), 因此对于Swift, 我们必须为每个monad实现地图。

还要注意, Option monad已经是诸如Swift和Scala之类的语言的事实上的标准, 因此我们在monad实现中使用略有不同的名称。

现在我们有了基本的Monad类, 让我们开始我们的Option monad实现。如前所述, 基本思想是期权持有某些价值(称为”某”)或根本不持有任何价值(“无”)。

pure方法只是将值提升为Some, 而flatMap方法检查Option的当前值-如果它为None则返回None, 如果它为Some且具有基础值, 则提取基础值, 将f()应用于它并返回结果。

请注意, 仅使用这两个函数和映射, 就不可能陷入空指针异常。 (这个问题可能会在我们执行flatMap方法时出现, 但这只是我们代码中的几行, 我们只需要检查一下。在那之后, 我们仅在数千个地方的整个代码中使用Option monad实现, 而不必完全不必担心null指针异常。)

莫纳德

让我们深入了解第二个monad:。这基本上与Option单声道相同, 但是Some称为Right, None称为Left。但是这次, Left也被允许具有基础价值。

我们需要这样做是因为表达异常非常方便。如果发生异常, 则Either的值将为Left(Exception)。如果值为Left, 则flatMap函数不会继续执行, 该函数会重复抛出异常的语义:如果发生异常, 我们将停止进一步执行。

JavaScript — 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

Python — 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

Ruby-Monad之一

require_relative './monad'

class Either < Monad

  attr_accessor :is_left, :value

  # pure :: a -> 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 < Either
  def initialize(value)
    @value = value
    @is_left = true
  end
end

class Right < Either
  def initialize(value)
    @value = value
    @is_left = false
  end
end

斯威夫特—莫纳德

import Foundation

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

Scala-Monad之一

package monad

sealed trait MyEither[+E, +A] {
  def flatMap[EE >: E, B](f: A => MyEither[EE, B]): MyEither[EE, B] =
    Monad[MyEither[EE, ?]].flatMap(this)(f)

  def map[EE >: E, B](f: A => B): MyEither[EE, B] =
    Monad[MyEither[EE, ?]].map(this)(f)
  
}
case class MyLeft[E](e: E) extends MyEither[E, Nothing]
case class MyRight[A](a: A) extends MyEither[Nothing, A]

// ...

implicit def myEitherMonad[E] = new Monad[MyEither[E, ?]] {
  def pure[A](a: A) = MyRight(a)

  def flatMap[A, B](ma: MyEither[E, A])(f: A => MyEither[E, B]): MyEither[E, B] = ma match {
    case MyLeft(a) => MyLeft(a)
    case MyRight(b) => f(b)
  }
}

另外请注意, 捕获异常很容易:你要做的就是从左到右映射。 (尽管如此, 为简洁起见, 我们在示例中并未这样做。)

Future Monad

让我们探索我们需要的最后一个monad :: Future monad。 Future monad基本上是一个值的容器, 该值现在可以使用, 也可以在不久的将来使用。你可以使用map和flatMap制作Future链, 它们将等待Future值解析, 然后再执行下一个取决于首先解析的值的代码。这与JS中Promises的概念非常相似。

我们现在的设计目标是将不同语言的现有异步API桥接到一个一致的基础上。事实证明, 最简单的设计方法是在$ constructor $中使用回调。

尽管回调设计在JavaScript和其他语言中引入了回调地狱问题, 但对我们而言这并不是问题, 因为我们使用monad。实际上, Promise对象(JavaScript解决回调地狱的基础)本身就是monad!

那么Future monad的构造函数呢?具有此签名:

构造函数::((err a-> void)-> void)-> Future(err a)

让我们将其分成几部分。首先, 让我们定义:

输入Callback = err a-> void

因此, 回调函数是一个将错误或解析值作为参数, 并且不返回任何内容的函数。现在, 我们的签名如下所示:

构造函数::(Callback-> void)-> Future(err a)

因此, 我们需要为其提供一个不返回任何内容并在异步计算解析为错误或某个值后立即触发回调的函数。看起来很容易为任何语言搭建桥梁。

至于Future monad本身的设计, 让我们看一下其内部结构。关键思想是拥有一个缓存变量, 该变量在解析FutureMonad之后将保存一个值, 否则将不保存任何值。你可以使用回调来订阅Future, 如果解析了值, 则会立即触发该回调, 否则将立即将该回调放入订户列表。

解析完Future之后, 此列表中的每个回调将在单独的线程中使用解析后的值精确触发一次(对于JS, 则作为事件循环中要执行的下一个函数。)请注意, 谨慎使用同步原语, 否则竞争条件是可能的。

基本流程是:启动作为构造函数参数提供的异步计算, 并将其回调指向我们的内部回调方法。同时, 你可以订阅Future monad并将回调放入队列。一旦计算完成, 内部回调方法将调用队列中的所有回调。如果你熟悉反应式扩展程序(RxJS, RxSwift等), 它们会使用非常相似的方法进行异步处理。

就像以前的Monad一样, FutureMonad的公共API由pure, map和flatMap组成。我们还需要几种方便的方法:

  1. 异步, 它具有同步阻止功能并在单独的线程上执行, 并且
  2. 遍历, 它采用值数组和将值映射到Future的函数, 并返回已解析值数组的Future

让我们看看效果如何:

JavaScript-未来Monad

import Monad from './monad';
import { Either, Left, Right } from './either';
import { none, Some } from './option';

export class Future extends Monad {
  // constructor :: ((Either err a -> void) -> void) -> Future (Either err a)
  constructor(f) {
    super();
    this.subscribers = [];
    this.cache = none;
    f(this.callback)
  }

  // callback :: Either err a -> void
  callback = (value) => {
    this.cache = new Some(value)
    while (this.subscribers.length) {
      const subscriber = this.subscribers.shift();
      subscriber(value)
    }
  }

  // subscribe :: (Either err a -> void) -> void
  subscribe = (subscriber) => 
    (this.cache === none ? this.subscribers.push(subscriber) : subscriber(this.cache.value))

  toPromise = () => new Promise(
    (resolve, reject) =>
      this.subscribe(val => val.isLeft() ? reject(val.value) : resolve(val.value))
  )

  // pure :: a -> Future a
  pure = Future.pure

  // flatMap :: (a -> Future b) -> Future b
  flatMap = f =>
    new Future(
      cb => this.subscribe(value => value.isLeft() ? cb(value) : f(value.value).subscribe(cb))
    )
}

Future.async = (nodeFunction, ...args) => {
  return new Future(cb => 
    nodeFunction(...args, (err, data) => err ? cb(new Left(err)) : cb(new Right(data)))
  );
}

Future.pure = value => new Future(cb => cb(Either.pure(value)))

// traverse :: [a] -> (a -> Future b) -> Future [b]
Future.traverse = list => f =>
  list.reduce(
    (acc, elem) => acc.flatMap(values => f(elem).map(value => [...values, value])), Future.pure([])
  )

Python-未来Monad

from monad import Monad
from option import nil, Some
from either import Either, Left, Right
from functools import reduce
import threading

class Future(Monad):
  # __init__ :: ((Either err a -> void) -> void) -> Future (Either err a)
  def __init__(self, f):
    self.subscribers = []
    self.cache = nil
    self.semaphore = threading.BoundedSemaphore(1)
    f(self.callback)

  # pure :: a -> Future a
  @staticmethod
  def pure(value):
    return Future(lambda cb: cb(Either.pure(value)))

  def exec(f, cb):
    try:
      data = f()
      cb(Right(data))
    except Exception as err:
      cb(Left(err))

  def exec_on_thread(f, cb):
    t = threading.Thread(target=Future.exec, args=[f, cb])
    t.start()

  def async(f):
    return Future(lambda cb: Future.exec_on_thread(f, cb))

  # flat_map :: (a -> Future b) -> Future b
  def flat_map(self, f):
    return Future(
      lambda cb: self.subscribe(
        lambda value: cb(value) if (value.is_left) else f(value.value).subscribe(cb)
      )
    )

  # traverse :: [a] -> (a -> Future b) -> Future [b]
  def traverse(arr):
    return lambda f: reduce(
      lambda acc, elem: acc.flat_map(
        lambda values: f(elem).map(
          lambda value: values + [value]
        )
      ), arr, Future.pure([]))

  # callback :: Either err a -> void
  def callback(self, value):
    self.semaphore.acquire()
    self.cache = Some(value)
    while (len(self.subscribers) > 0):
      sub = self.subscribers.pop(0)
      t = threading.Thread(target=sub, args=[value])
      t.start()
    self.semaphore.release()
  
  # subscribe :: (Either err a -> void) -> void
  def subscribe(self, subscriber):
    self.semaphore.acquire()
    if (self.cache.defined):
      self.semaphore.release()
      subscriber(self.cache.value)
    else:
      self.subscribers.append(subscriber)
self.semaphore.release()

Ruby-未来世界

require_relative './monad'
require_relative './either'
require_relative './option'

class Future < Monad
  attr_accessor :subscribers, :cache, :semaphore

  # initialize :: ((Either err a -> void) -> void) -> Future (Either err a)
  def initialize(f)
    @subscribers = []
    @cache = $none
    @semaphore = Queue.new
    @semaphore.push(nil)
    f.call(method(:callback))
  end

  # pure :: a -> Future a
  def self.pure(value)
    Future.new(-> (cb) { cb.call(Either.pure(value)) })
  end

  def self.async(f, *args)
    Future.new(-> (cb) {
      Thread.new {
        begin
          cb.call(Right.new(f.call(*args)))
        rescue => e
          cb.call(Left.new(e))
        end  
      }
    })
  end

  # pure :: a -> Future a
  def pure(value)
    self.class.pure(value)
  end

  # flat_map :: (a -> Future b) -> Future b
  def flat_map(f)
    Future.new(-> (cb) { 
      subscribe(-> (value) {
        if (value.is_left)
          cb.call(value) 
        else
          f.call(value.value).subscribe(cb)
        end
      }) 
    })
  end

  # traverse :: [a] -> (a -> Future b) -> Future [b]
  def self.traverse(arr, f)
    arr.reduce(Future.pure([])) do |acc, elem|
      acc.flat_map(-> (values) {
        f.call(elem).map(-> (value) { values + [value] })
      })
    end
  end

  # callback :: Either err a -> void
  def callback(value)
    semaphore.pop
    self.cache = Some.new(value)
    while (subscribers.count > 0)
      sub = self.subscribers.shift
      Thread.new {
        sub.call(value)
      }
    end
    semaphore.push(nil)
  end
  
  # subscribe :: (Either err a -> void) -> void
  def subscribe(subscriber)
    semaphore.pop
    if (self.cache.defined)
      semaphore.push(nil)
      subscriber.call(cache.value)
    else
      self.subscribers.push(subscriber)
      semaphore.push(nil)
    end
  end
end

斯威夫特-Future Monad

import Foundation

let background = DispatchQueue(label: "background", attributes: .concurrent)

class Future<Err, A> {
  typealias Callback = (Either<Err, A>) -> Void
  
  var subscribers: Array<Callback> = Array<Callback>()
  var cache: Maybe<Either<Err, A>> = .None
  var semaphore = DispatchSemaphore(value: 1)

  lazy var callback: Callback = { value in
    self.semaphore.wait()
    self.cache = .Some(value)
    while (self.subscribers.count > 0) {
      let subscriber = self.subscribers.popLast()
      background.async {
        subscriber?(value)
      }
    }
    self.semaphore.signal()
  }
  
  init(_ f: @escaping (@escaping Callback) -> Void) {
    f(self.callback)
  }
  
  func subscribe(_ cb: @escaping Callback) {
    self.semaphore.wait()
    switch cache {
    case .None:
      subscribers.append(cb)
      self.semaphore.signal()
    case .Some(let value):
      self.semaphore.signal()
      cb(value)
    }
  }
  
  static func pure<B>(_ value: B) -> Future<Err, B> {
    return Future<Err, B> { $0(Either<Err, B>.pure(value)) }
  }
  
  func flatMap<B>(_ f: @escaping (A) -> Future<Err, B>) -> Future<Err, B> {
    return Future<Err, B> { [weak self] cb in
      guard let this = self else { return }
      this.subscribe { value in
        switch value {
        case .Left(let err):
          cb(Either<Err, B>.Left(err))
        case .Right(let x):
          f(x).subscribe(cb)
        }
      }
    }
  }
  
  func map<B>(_ f: @escaping (A) -> B) -> Future<Err, B> {
    return self.flatMap { Future<Err, B>.pure(f($0)) }
  }
  
  static func traverse<B>(_ list: Array<A>, _ f: @escaping (A) -> Future<Err, B>) -> Future<Err, Array<B>> {
    return list.reduce(Future<Err, Array<B>>.pure(Array<B>())) { (acc: Future<Err, Array<B>>, elem: A) in
      return acc.flatMap { elems in
        return f(elem).map { val in
          return elems + [val]
        }
      }
    }
  }
}

Scala-未来Monad

package monad

import java.util.concurrent.Semaphore

class MyFuture[A] {
  private var subscribers: List[MyEither[Exception, A] => Unit] = List()
  private var cache: MyOption[MyEither[Exception, A]] = MyNone
  private val semaphore = new Semaphore(1)

  def this(f: (MyEither[Exception, A] => Unit) => Unit) {
    this()
    f(this.callback _)
  }

  def flatMap[B](f: A => MyFuture[B]): MyFuture[B] =
    Monad[MyFuture].flatMap(this)(f)

  def map[B](f: A => B): MyFuture[B] =
    Monad[MyFuture].map(this)(f)

  def callback(value: MyEither[Exception, A]): Unit = {
    semaphore.acquire
    cache = MySome(value)
    subscribers.foreach { sub =>
      val t = new Thread(
        new Runnable {
          def run: Unit = {
            sub(value)
          }
        }
      )
      t.start
    }
    subscribers = List()
    semaphore.release
  }

  def subscribe(sub: MyEither[Exception, A] => Unit): Unit = {
    semaphore.acquire
    cache match {
      case MyNone =>
        subscribers = sub :: subscribers
        semaphore.release
      case MySome(value) =>
        semaphore.release
        sub(value)
    }
  }
}

object MyFuture {
  def async[B, C](f: B => C, arg: B): MyFuture[C] =
    new MyFuture[C]({ cb =>
      val t = new Thread(
        new Runnable {
          def run: Unit = {
            try {
              cb(MyRight(f(arg)))
            } catch {
              case e: Exception => cb(MyLeft(e))
            }
          }
        }
      )
      t.start
    })

  def traverse[A, B](list: List[A])(f: A => MyFuture[B]): MyFuture[List[B]] = {
    list.foldRight(Monad[MyFuture].pure(List[B]())) { (elem, acc) =>
      Monad[MyFuture].flatMap(acc) ({ values =>
        Monad[MyFuture].map(f(elem)) { value => value :: values }
      })
    }
  }
}

// ...

implicit val myFutureMonad = new Monad[MyFuture] {
  def pure[A](a: A): MyFuture[A] = 
    new MyFuture[A]({ cb => cb(myEitherMonad[Exception].pure(a)) })

  def flatMap[A, B](ma: MyFuture[A])(f: A => MyFuture[B]): MyFuture[B] =
    new MyFuture[B]({ cb =>
      ma.subscribe(_ match {
        case MyLeft(e) => cb(MyLeft(e))
        case MyRight(a) => f(a).subscribe(cb)
      })
    })
}

现在, 请注意, 未来的公共API如何不包含任何低级详细信息, 例如线程, 信号量或任何其他东西。你所需要做的基本上就是为回调提供一些东西, 仅此而已!

用Monads编写程序

好的, 让我们尝试使用我们的monad编写一个实际的程序。假设我们有一个包含URL列表的文件, 我们想并行获取每个URL。然后, 为了简洁起见, 我们希望将响应每个剪切为200个字节并打印出结果。

我们首先将现有的语言API转换为monadic接口(请参见函数readFile和fetch)。现在我们有了我们可以将它们组合起来以获得一个链的最终结果。请注意, 链条本身是超级安全的, 因为所有血腥细节都包含在monad中。

JavaScript —示例Monad程序

import { Future } from './future';
import { Either, Left, Right } from './either';
import { readFile } from 'fs';
import https from 'https';

const getResponse = url => 
  new Future(cb => https.get(url, res => {
    var body = '';
    res.on('data', data => body += data);
    res.on('end', data => cb(new Right(body)));
    res.on('error', err => cb(new Left(err)))
  }))

const getShortResponse = url => getResponse(url).map(resp => resp.substring(0, 200))

Future
  .async(readFile, 'resources/urls.txt')
  .map(data => data.toString().split("\n"))
  .flatMap(urls => Future.traverse(urls)(getShortResponse))
.map(console.log)

Python-示例Monad程序

import http.client
import threading
import time
import os
from future import Future
from either import Either, Left, Right

conn = http.client.HTTPSConnection("en.wikipedia.org")

def read_file_sync(uri):
  base_dir = os.path.dirname(__file__) #<-- absolute dir the script is in
  path = os.path.join(base_dir, uri)
  with open(path) as f:
    return f.read()

def fetch_sync(uri):
  conn.request("GET", uri)
  r = conn.getresponse()
  return r.read().decode("utf-8")[:200]

def read_file(uri):
  return Future.async(lambda: read_file_sync(uri))

def fetch(uri):
  return Future.async(lambda: fetch_sync(uri))

def main(args=None):
  lines = read_file("../resources/urls.txt").map(lambda res: res.splitlines())
  content = lines.flat_map(lambda urls: Future.traverse(urls)(fetch))
  output = content.map(lambda res: print("\n".join(res)))

if __name__ == "__main__":
main()

Ruby-示例Monad程序

require './lib/future'
require 'net/http'
require 'uri'

semaphore = Queue.new

def read(uri)
  Future.async(-> () { File.read(uri) })
end

def fetch(url)
  Future.async(-> () {
    uri = URI(url)
    Net::HTTP.get_response(uri).body[0..200]
  })
end

read("resources/urls.txt")
  .map(-> (x) { x.split("\n") })
  .flat_map(-> (urls) {
    Future.traverse(urls, -> (url) { fetch(url) })
  })
  .map(-> (res) { puts res; semaphore.push(true) })

semaphore.pop

Swift-示例Monad程序

import Foundation

enum Err: Error {
  case Some(String)
}

func readFile(_ path: String) -> Future<Error, String> {
  return Future<Error, String> { callback in
    background.async {
      let url = URL(fileURLWithPath: path)
      let text = try? String(contentsOf: url)
      if let res = text {
        callback(Either<Error, String>.pure(res))
      } else {
        callback(Either<Error, String>.Left(Err.Some("Error reading urls.txt")))
      }
    }
  }
}

func fetchUrl(_ url: String) -> Future<Error, String> {
  return Future<Error, String> { callback in
    background.async {
      let url = URL(string: url)
      let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
        if let err = error {
          callback(Either<Error, String>.Left(err))
          return
        }
        guard let nonEmptyData = data else {
          callback(Either<Error, String>.Left(Err.Some("Empty response")))
          return
        }
        guard let result = String(data: nonEmptyData, encoding: String.Encoding.utf8) else {
          callback(Either<Error, String>.Left(Err.Some("Cannot decode response")))
          return
        }
        let index = result.index(result.startIndex, offsetBy: 200)
        callback(Either<Error, String>.pure(String(result[..<index])))
      }
      task.resume()
    }
  }
}

var result: Any = ""
let _ = readFile("\(projectDir)/Resources/urls.txt")
    .map { data -> [String] in
      data.components(separatedBy: "\n").filter { (line: String) in !line.isEmpty }
    }.flatMap { urls in
        return Future<Error, String>.traverse(urls) { url in
            return fetchUrl(url)
        }
    }.map { responses in
      print(responses)
    }

RunLoop.main.run()

Scala-Monad示例程序

import scala.io.Source
import java.util.concurrent.Semaphore
import monad._

object Main extends App {
  val semaphore = new Semaphore(0)

  def readFile(name: String): MyFuture[List[String]] =
    MyFuture.async[String, List[String]](filename => Source.fromResource(filename).getLines.toList, name)
  
  def fetch(url: String): MyFuture[String] =
    MyFuture.async[String, String](
      uri => Source.fromURL(uri).mkString.substring(0, 200), url
    )
  
  val future = for {
    urls <- readFile("urls.txt")
    entries <- MyFuture.traverse(urls)(fetch _)
  } yield { 
    println(entries)
    semaphore.release
  }

  semaphore.acquire
}

在那里, 你可以在实践中找到monad解决方案。你可以在GitHub上找到包含本文所有代码的仓库。

开销:完成。好处:进行中

对于这个简单的基于monad的程序, 使用我们之前编写的所有代码可能看起来有些矫kill过正。但这只是初始设置, 它的大小将保持不变。想象一下, 从现在开始, 你可以使用monad编写很多异步代码, 而不必担心线程, 竞争条件, 信号量, 异常或空指针!太棒了!

赞(0)
未经允许不得转载:srcmini » JavaScript,Python,Ruby,Swift和Scala中的Option/Maybe,Ether和Future Monad

评论 抢沙发

评论前必须登录!