<路径 clip-rule="evenodd" d="M33.377 4.574a3.508 3.508 0 0 0-2.633-1.126c-1 0-1.993.67-2.604 1.334l.002-1.24-1.867-.002-.02 10.17v.133l1.877.002.008-3.18c.567.611 1.464.97 2.462.973 1.099 0 2.022-.377 2.747-1.117.73-.745 1.1-1.796 1.103-3.002.003-1.232-.358-2.222-1.075-2.945Zm-3.082.55c.637 0 1.176.23 1.602.683.438.438.663 1.012.66 1.707-.003.7-.22 1.33-.668 1.787-.428.438-.964.661-1.601.661-.627 0-1.15-.22-1.6-.666-.445-.46-.662-1.086-.662-1.789.003-.695.227-1.27.668-1.708a2.13 2.13 0 0 1 1.596-.675h.005Zm5.109-.067-.008 4.291c-.002.926.263 1.587.784 1.963.325.235.738.354 1.228.354.376 0 .967-.146.967-.146l-.168-1.564s-.43.133-.64-.01c-.198-.136-.296-.428-.296-.866l.008-4.022 1.738.002.002-1.492-1.738-.002.005-2.144-1.874-.002-.005 2.143-1.573-.002 1.57 1.497ZM20.016 1.305h-9.245l-.002 1.777h3.695l-.016 8.295v.164l1.955.002-.008-8.459 3.621-.002V1.305Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M10.06 5.844 7.277 3.166 4.015.03 2.609 1.374l2.056 1.978-4.51 4.313 6.065 5.831 1.387-1.327-2.073-1.994 4.526-4.331ZM4.274 8.7a.211.211 0 0 1-.124 0c-.04-.013-.074-.03-.15-.102l-.817-.787c-.072-.069-.092-.104-.105-.143a.187.187 0 0 1 0-.12c.013-.039.03-.07.105-.143L5.76 4.938c.072-.07.108-.09.15-.099a.21.21 0 0 1 .123 0c.041.012.075.03.15.101L7 5.727c.072.07.093.104.103.144.013.04.013.08 0 .119-.013.04-.03.072-.106.143L4.422 8.601a.325.325 0 0 1-.147.099Z" fill="#204ECF" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M24.354 4.622a3.94 3.94 0 0 0-2.876-1.149 4.1 4.1 0 0 0-2.829 1.084c-.804.725-1.214 1.733-1.217 2.992-.002 1.26.405 2.267 1.207 2.995a4.114 4.114 0 0 0 2.832 1.094c.04.002.082.002.123.002a3.967 3.967 0 0 0 2.75-1.138c.538-.532 1.183-1.473 1.186-2.938.002-1.465-.637-2.408-1.176-2.942Zm-.59 2.94c-.003.73-.228 1.334-.671 1.794-.441.458-.99.69-1.633.69a2.166 2.166 0 0 1-1.614-.697c-.43-.45-.65-1.057-.65-1.797s.222-1.344.655-1.795a2.17 2.17 0 0 1 1.617-.69c.64 0 1.189.235 1.63.698.443.46.668 1.064.665 1.797ZM41.15 6.324c0-.458.25-1.465 1.632-1.465.49 0 .768.159 1.003.347.227.18.34.626.34.994v.174l-2.282.341C40.035 6.98 39 7.913 38.993 9.28c-.002.708.266 1.314.777 1.76.503.438 1.191.67 2.004.673 1.023 0 1.792-.354 2.341-1.084.003.31.003.621.003.91h1.903l.013-5.246c.002-.856-.289-1.685-.864-2.14-.567-.449-1.31-.679-2.386-.681h-.015c-.82 0-1.69.208-2.274.695-.689.572-1.027 1.478-1.027 2.178l1.682-.02Zm.864 3.814c-.676-.002-1.115-.371-1.112-.938.003-.589.43-.933 1.346-1.081l1.875-.305v.017c-.005 1.36-.87 2.307-2.102 2.307h-.008Zm4.917-8.712-.018 10.058v.044l1.684.005.018-10.06v-.045l-1.684-.002Zm2.654 9.491c0-.173.062-.322.19-.445a.645.645 0 0 1 .462-.186c.18 0 .338.062.465.186a.596.596 0 0 1 .193.445.583.583 0 0 1-.193.443.644.644 0 0 1-.465.183.634.634 0 0 1-.461-.183.59.59 0 0 1-.191-.443Zm.108 0c0 .146.052.273.158.376a.54.54 0 0 0 .389.154.539.539 0 0 0 .547-.53.498.498 0 0 0-.16-.373.531.531 0 0 0-.387-.156.531.531 0 0 0-.387.155.497.497 0 0 0-.16.374Zm.702.344-.176-.3h-.118v.3h-.109v-.688h.292c.144 0 .23.082.23.196 0 .096-.076.168-.176.188l.178.304h-.121Zm-.294-.596v.21h.167c.093 0 .14-.034.14-.104 0-.072-.047-.106-.14-.106h-.167Z" fill="#262D3D" fill-rule="evenodd">作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
<路径 d="m2.3438,5.6562l-2.3438,2.3438,2.3438,2.3438v3.3137h3.3137l2.3425,2.3425,2.3425-2.3425h3.315v-3.315l2.3425-2.3425-2.3425-2.3425v-3.3137h-3.3138l-2.3437-2.3438-2.3438,2.3438h-3.3125v3.3125Zm5.0488,2.7528l2.754-2.7654.9705.9739-3.7245,3.7399-.9705-.9739-1.3672-1.3733.9705-.9752,1.3672,1.3739Z" fill="currentColor"><路径 class="text-white dark:text-transparent" clip-rule="evenodd" d="M10.1465 5.64374L7.39254 8.4091L6.02535 7.03518L5.05485 8.01039L6.42204 9.38364L7.39254 10.3575L11.117 6.61761L10.1465 5.64374Z" fill="currentColor" fill-rule="evenodd">验证专家 在工程
<路径 clip-rule="evenodd" d="M12.785 3.30422L8.19592 0.680947C7.8317 0.472777 7.41948 0.363281 7 0.363281C6.58052 0.363281 6.1683 0.472777 5.80408 0.680947L1.21446 3.30422C0.463414 3.73353 0 4.53222 0 5.3977V10.6014C2.84061e-05 11.0265 0.112371 11.444 0.325646 11.8116C0.538921 12.1793 0.845549 12.484 1.21446 12.6949L5.80408 15.3182C6.16837 15.5265 6.5807 15.636 7.00029 15.636C7.41987 15.636 7.8322 15.5265 8.19649 15.3182L12.785 12.6949C13.154 12.4841 13.4607 12.1794 13.6741 11.8117C13.8875 11.4441 13.9999 11.0265 14 10.6014V5.3977C13.9999 4.97258 13.8875 4.55504 13.6741 4.1874C13.4607 3.81975 13.154 3.51507 12.785 3.30422ZM5.09524 8.68011L4.60062 11.3333L7.19048 10.0807L9.78033 11.3333L9.28571 8.68011L11.381 6.80108L8.4854 6.41398L7.19048 4L5.89555 6.41398L3 6.80108L5.09524 8.68011Z" fill="currentColor" fill-rule="evenodd">15 的经验

Alexey (MEcon)精通几种语言,更喜欢函数式编程, 尤其是Scala, 为了减少浪费在捉虫子上的时间.

本monad教程简要介绍了monad,并展示了如何在五种不同的编程语言中实现最有用的monad——如果您正在寻找monad JavaScript,单子在 Python,单子在 Ruby,单子在 斯威夫特,和/或单子 Scala,或者比较任何实现,您正在阅读正确的文章!

使用这些单子,你将摆脱一系列的错误,如空指针异常, 未处理的异常, 以及竞态条件.

以下是我要介绍的内容:

  • 范畴论简介
  • 单子的定义
  • 选项(“Maybe”)单子的实现, 要么单子, 和未来单子, 再加上一个利用它们的示例程序, 在JavaScript中, Python, Ruby, 斯威夫特, 和Scala

让我们开始吧! 我们的第一站是范畴论,它是单子的基础.

范畴论概论

范畴论是20世纪中叶蓬勃发展起来的一个数学领域. 现在它是包括单子在内的许多函数式编程概念的基础. 让我们快速看一下范畴论的一些概念, 针对软件开发术语进行了调优.

定义a有三个核心概念 类别:

  1. 类型 就像我们在静态类型语言中看到的那样吗. 例子: Int, 字符串, , 等.
  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 = even \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$映射到自身的函数.

我们来看一个简单的分类.

一个简单的类别,包括字符串、Int和双,以及其中的一些函数.

旁注:我们假设 Int, 字符串 所有其他类型都保证非空I.e.,空值不存在.

旁注2:这实际上只是 部分 属于某一类的, 但这就是我们讨论的全部内容, 因为它有我们需要的所有基本部分,而且这样的图表不那么混乱. 真正的类别也将具有所有组合函数,如$roundTo字符串: 双 \to 字符串 = intTo字符串 \cdot round$, 满足范畴的构成子句.

您可能会注意到,这类函数非常简单. 事实上,这些函数几乎不可能有bug. 没有空,没有异常,只有算术和内存处理. 因此,唯一可能发生的糟糕情况是处理器或内存故障——在这种情况下,无论如何都需要使程序崩溃——但这种情况很少发生.

如果我们所有的代码都能在这样的稳定水平上工作,那不是很好吗? 绝对! 但是举例来说,I/O呢? 我们绝对不能没有它. 这就是monad解决方案的拯救之处:它们将所有不稳定的操作隔离到超小且经过良好审计的代码片段中,然后您可以在整个应用程序中使用稳定的计算!

进入了单体

我们把不稳定的行为称为I/O a 副作用. 现在我们希望能够处理所有之前定义的函数,比如 长度 像这样的类型 字符串 在这种情况下以稳定的方式 副作用.

因此,让我们从一个空的类别$M[A]$开始,并将其转换为一个类别,该类别将具有具有特定类型副作用的值,也将具有没有副作用的值. 假设我们已经定义了这个类别,并且它是空的. 现在我们对它无能为力, 为了使它有用, 我们将遵循以下三个步骤:

  1. 用类别$A$的类型值填充它,如 字符串, Int, 等. (下图中的绿色方框)
  2. 一旦我们有了这些值, 我们仍然不能对他们做任何有意义的事情, 所以我们需要一种方法从$ a $取每个函数$f: T \到$ U$,并创建一个函数$g: M[T] \到M[U]$(下图中的蓝色箭头). 一旦我们有了这些函数, 我们可以用类别$M[A]$中的值来做我们在类别$A$中能够做的一切.
  3. 现在我们有了一个全新的$M[a]$类别, 出现了一类新的函数,其签名为$h: T \to M[U]$(下图中的红色箭头). 作为第一步代码库的一部分,它们作为促进价值的结果而出现.e., we write them as needed; these are the main things that will differentiate working with $M[A]$ versus working with $A$. 最后一步将是使这些函数在$M[A]$ i中的类型上也能很好地工作.e.,可以从$h: T \to m [U]$推导出函数$m: m [T] \to m [U]$

创建新类别:类别a和M[a], 加上从a的双到M[a] Int的红色箭头, labelled "roundAsync". M[A]重用了此时A的所有值和函数.

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

  1. 第一个称为$纯$,为稳定类别的每个值定义:$纯: T \to M[T]$. 生成的$M[T]$值不会有任何副作用,因此这个函数被称为$纯$. E.g.对于I/O单子,$纯$将立即返回一些值,没有失败的可能性.
  2. 第二个函数称为构造函数,与纯函数不同,它返回带有一些副作用的M[T]. 一个用于异步I/O单子的构造函数美元的例子可以是一个从web获取数据并返回数据的函数 字符串. 在这种情况下,构造函数美元返回的值类型为$M[字符串]$.

现在我们有两种方法将价值提升到$M[A]$, 作为程序员,您可以选择使用哪个函数, 这取决于你的项目目标. 让我们考虑一个示例:您想获取一个HTML页面,如http://www.toptal.Com/javascript/option-maybe-either-future-monads-js,为此你创建一个函数$获取$. 因为在获取数据时任何事情都可能出错——想想网络故障等等.-您将使用$M[字符串]$作为此函数的返回类型. 所以它看起来就像$获取: 字符串 \to M[字符串]$在函数体的某个地方,我们将使用构造函数美元 for $M$.

现在让我们假设我们创建了一个用于测试的模拟函数:$获取Mock: 字符串 \to M[字符串]$. 它仍然有相同的签名, 但是这次我们只是将生成的HTML页面注入到$获取Mock$的主体中,而不做任何不稳定的网络操作. 所以在这种情况下,我们只是在$获取Mock$的实现中使用$纯$.

作为下一步, 我们需要一个函数,可以安全地将任意函数从类别$ a $提升到$M[a]$(图中的蓝色箭头). 这个函数叫做$map: (T \to U) \to (M[T] \to M[U])$.

现在我们有了一个类别(如果我们使用$构造函数,它可能会产生副作用), 它也有稳定类的所有函数, 这意味着它们在M[A]$中也很稳定. 您可能注意到,我们显式地引入了另一类函数,如$f: T \to M[U]$. E.g., $纯$和构造函数美元就是用于$U = T$的此类函数的例子, 但显然还有更多, 例如,如果我们使用$纯$,然后使用$map$. 一般来说,我们需要一种方法来处理任意形式的函数f: T \to M[U]$.

如果我们想创建一个基于f的新函数,它可以应用于M[T], 我们可以尝试使用$map$. 这就引出函数$g: M[T] \到M[M[U]]$, 这是不好的,因为我们不想有更多的类别$M[M[A]]$. 来处理这个问题, 我们引入最后一个函数:$flatMap: (T \to M[U]) \to (M[T] \to M[U])$.

但我们为什么要这么做呢? 假设我们在第二步i之后.e.,我们有$纯$、构造函数美元和$map$. 假设我们想从total抓取一个HTML页面.com,然后扫描那里的所有url并获取它们. 我会做一个函数$获取: 字符串 \to M[字符串]$,它只获取一个URL并返回一个HTML页面.

然后,我将此函数应用于URL并从total获取页面.$x: M[字符串]$. 现在,我对$x$做一些转换,最后得到某个URL $u: M[字符串]$. 我想对它应用函数$获取$,但是我不能,因为它的类型是$字符串$,而不是$M[字符串]$. 这就是为什么我们需要$flatMap$来转换$获取: 字符串 \to M[字符串]$到$m_获取: M[字符串] \to M[字符串]$.

现在我们已经完成了这三个步骤, 实际上,我们可以组合任何需要的值转换. 例如, 如果您的值$x$的类型为$M[T]$和$f: T \到$ U$, 您可以使用$map$将$f$应用于值$x$,并获得类型$M[U]$的值$y$. 这样,任何值的转换都可以以100%无bug的方式完成, 只要$纯$, 构造函数美元, $map$和$flatMap$实现是无bug的.

因此,与其每次在代码库中遇到一些令人讨厌的效果,不如处理它们, 您只需要确保正确实现了这四个函数. 在节目的最后, 您将只得到一个$M[X]$,您可以安全地打开值$X$并处理所有错误情况.

这就是monad:一个实现$纯$、$map$和$flatMap$的东西. (实际上,$map$可以由$纯$和$flatMap$派生而来, 但它是非常有用和广泛的功能, 所以我没有从定义中省略它.)

期权单子,a.k.a. 也许单子

好了,让我们深入了解单子的实际实现和使用. 第一个真正有用的单子是Option单子. 如果你是从经典编程语言学来的, 由于臭名昭著的空指针错误,您可能遇到过许多崩溃. null的发明者Tony Hoare称这项发明为“十亿美元的错误”:

这导致了无数的错误, 漏洞, 系统崩溃了, 在过去的四十年里,它们可能造成了十亿美元的痛苦和损害.

让我们试着改进一下. Option单子要么保存一些非空值,要么没有值. 非常类似于空值, 但是有了这个单子, 我们可以安全地使用定义良好的函数,而不必担心空指针异常. 让我们看一下不同语言的实现:

JavaScript-Option 单孢体/Maybe 单孢体


类单孢体 {
  // 纯 :: a -> M a
  纯 = () => { throw "纯 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 => 这.flatMap(x => 新 这.纯(f (x)))
}

导出类选项扩展单孢体 {
  // 纯 :: a -> Option a
  纯 = (值) => {
    If ((value === null) || (value === un定义)) {
      返回所有;
    }
    返回新 一些(值)
  }

  // flatMap :: # Option a -> (a -> Option b) -> Option b
  flatMap = f => 
    这.构造函数.name === '无' ? 
    没有: 
    f(这.值)

  // equals :: # M a -> M a -> boolean
  equals = (x) => 这.to字符串 () === x.to字符串 ()
}

类没有一个扩展选项{
  to字符串 () {
    返回“没有”;
  }
}
//缓存无类值
export const none = 新 none ()
Option.Pure =无.纯

导出类扩展选项{
  构造函数(值){
    超级();
    这.Value = Value;
  }

  to字符串 () {
    返回一些($ {.值})”
  }
}

Python-Option 单孢体/Maybe 单孢体


类单子:
  # 纯 :: a -> M a
  @staticmethod
  def纯(x):
    raise Exception("纯 method需要被实现")
  
  # flat_map :: # M a -> (a -> M b) -> M b
  Def flat_map (自我, f):
    引发异常("flat_map方法需要被实现")

  # map :: # M a -> (a -> b) -> M b
  Def 地图(自我, f):
    回归自我.Flat_map (lambda x: 自我.纯(f (x)))

类选项(单轴):
  # 纯 :: a -> Option a
  @staticmethod
  def纯(x):
    返回一些(x)

  # flat_map :: # Option a -> (a -> Option b) -> Option b
  Def flat_map (自我, f):
    如果自我.定义:
      返回f(自我.值)
    其他:
      返回nil


类(选择):
  Def __init__(自我, 值):
    自我.Value = Value
    自我.defined = True

类Nil(选项):
  def __init__(自我):
    自我.value =无
    自我.defined = False

nil = nil ()

Ruby-Option 单孢体/Maybe 单孢体


类的单子
  # 纯 :: a -> M a
  def自我.纯(x)
    引发st和derror ("纯 method需要被实现")
  结束
  
  # 纯 :: a -> M a
  def纯(x)
    自我.class.纯(x)
  结束
    
  def flat_map (f)
    引发st和derror ("flat_map方法需要被实现")
  结束

  # map :: # M a -> (a -> b) -> M b
  def地图(f)
    flat_map (-> (x) { 纯(f.调用(x))})
  结束
结束

class Option < 单孢体

  Attr_accessor:定义,:value

  # 纯 :: a -> Option a
  def自我.纯(x)
    一些.新(x)
  结束

  # 纯 :: a -> Option a
  def纯(x)
    一些.新(x)
  结束
  
  # flat_map :: # Option a -> (a -> Option b) -> Option b
  def flat_map (f)
    如果定义
      f.调用(值)
    其他的
      没有美元
    结束
  结束
结束

class 一些 < Option
  def初始化(值)
    @Value = Value
    @defined = true
  结束
结束

class 没有一个 < Option
  def初始化()
    @defined = false
  结束
结束

没有美元 = none.新()

快速选项单孢体/也许单孢体

Scala-Option 单孢体/Maybe 单孢体


导入的语言.higherKinds

性状单子[M[_]] {
  def 纯[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 => 纯(f (x)))
}
对象单子{
def apply[F[_]](隐式M: 单孢体[F]): 单孢体[F] = M
  
  隐式val myOption单孢体 =新的单子(MyOption) {
    def纯 [A](A: A) = My一些(A)
    def flatMap[A, B](ma: MyOption[A])(f: A => MyOption[B]): MyOption[B] = ma match {
      情况下 My没有一个 => My没有一个
      情况下 My一些(a) => f(a)
    }
  }
}

MyOption[+A] {
  def flatMap[B](f: A => MyOption[B]): MyOption[B] =
    单子(MyOption).flatMap(这)(f)

  def map[B](f: A => B): MyOption[B] =
    单子(MyOption).map () (f)
}

情况下对象My没有一个扩展MyOption[none]
情况下类My一些[A](x: A)扩展MyOption[A]

我们从实现a开始 单孢体 类,它将成为我们所有monad实现的基础. 拥有这个类非常方便,因为只需实现它的两个方法-flatMap-对于特定的单子,您将免费获得许多方法(我们将它们限制为简单的 map 方法,但通常还有许多其他有用的方法,如 序列遍历 的数组 单孢体s).

我们可以表示 map 的组成 flatMap. 你可以从 flatMap$flatMap: (T \to M[U]) \to (M[T] \to M[U])$表明它非常接近$map: (T \to U) \to (M[T] \to M[U])$. 不同之处在于中间额外的$M$,但我们可以使用 函数将$U$转换为$M[U]$. 我们用这种方式表达 map 就…而言 flatMap.

这在Scala中工作得很好,因为它有一个高级的类型系统. 它也适用于JS、Python和Ruby,因为它们是动态类型的. 不幸的是, 这对斯威夫特不起作用, 因为它是静态类型的,没有高级类型特性,比如 higher-kinded类型,所以对于斯威夫特,我们必须实现 map 对于每个单子.

还要注意,Option单子已经是一个 事实上的 斯威夫特和Scala等语言的标准, 所以我们对单子的实现使用稍微不同的名字.

现在我们有了一个底 单孢体 类,让我们来看看Option单子的实现. 如前所述,基本思想是Option要么保存一些值(称为 一些)或者根本没有任何价值(没有一个).

方法只是将值提升为 一些,而 flatMap 方法检查控件的当前值 Option -如果是 没有一个 然后返回 没有一个,如果是 一些 对于基础值,它提取基础值,应用 f() 并返回一个结果.

注意,仅使用这两个函数和 map因此,不可能出现空指针异常——永远不会. (这个问题 可以 在我们实施的过程中可能会出现 flatMap 方法,但那只是我们代码中检查一次的几行. 在那之后, 我们只需在代码的数千个地方使用Option monad实现,根本不用担心空指针异常.)

任一方单子

让我们深入研究第二个单子:要么. 这与Option单子基本相同,但使用 一些 被称为 正确的没有一个 被称为 . 但这一次, 也允许有一个潜在的值.

我们需要它,因为它非常方便地表达抛出异常. 如果发生异常,则 要么左(异常). 的 flatMap 如果值为,则函数不会进度 , 它重复抛出异常的语义:如果发生了异常, 我们停止进一步的执行.

JavaScript-要么单子


导入单孢体./单子”;

导出类继承单孢体 {
  // 纯 :: a -> 要么 a
  纯 = (值) => {
    返回新 (价值)
  }

  // flatMap :: # 要么 a -> (a -> 要么 b) -> 要么 b
  flatMap = f => 
    这.is左 () ? 
    这样的: 
    f(这.值)

  is左 = () => 这.构造函数.name === '左'
}

导出类左扩展要么 {
  构造函数(值){
    超级();
    这.Value = Value;
  }

  to字符串 () {
    返回“左($ {.值})”
  }
}

导出类正确的扩展了要么 {
  构造函数(值){
    超级();
    这.Value = Value;
  }

  to字符串 () {
    返回的权利($ {.值})”
  }
}

// attempt :: (() -> a) -> M a 
要么.attempt = f => {
    尝试{
      返回 新 正确的(f())
    } catch(e) {
      返回 新 左(e)
    }
}
要么.纯 = (新 左(null)).纯

Python-要么单子


从monad导入monad

类(单轴):
  # 纯 :: a -> 要么 a
  @staticmethod
  def纯(价值):
    返回正确的(值)

  # flat_map :: # 要么 a -> (a -> 要么 b) -> 要么 b
  Def flat_map (自我, f):
    如果自我.is_left:
      回归自我
    其他:
      返回f(自我.值)

类左():
  Def __init__(自我, 值):
    自我.Value = Value
    自我.is_left = True

类正确():
  Def __init__(自我, 值):
    自我.Value = Value
自我.is_left = False

Ruby-要么单子


require_relative”./单轴的

class 要么 < 单孢体

  Attr_accessor:is_left, value

  # 纯 :: a -> 要么 a
  def自我.纯(值)
    正确的.新(值)
  结束

  # 纯 :: a -> 要么 a
  def纯(值)
    自我.class.纯(值)
  结束

  # flat_map :: # 要么 a -> (a -> 要么 b) -> 要么 b
  def flat_map (f)
    如果is_left
      自我
    其他的
      f.调用(值)
    结束
  结束
结束

class 左 < 要么
  def初始化(值)
    @Value = Value
    @is_left = true
  结束
结束

class 正确的 < 要么
  def初始化(值)
    @Value = Value
    @is_left = false
  结束
结束

斯威夫特-要么单子


进口的基础

enum 要么 {
  例左(一个)
  情况下正确的(B)
  static func 纯(_ value: C) -> 要么 {
    返回 要么.(价值)
  }
  
  func flatMap(_ f: (B) -> 要么) -> 要么 {
    切换自{
    情况下 .左(让x):
      返回 要么.左(x)
    情况下 .正确的(让x):
      返回f (x)
    }
  }
  
  func map(f: (B) -> C) -> 要么 {
    回归自我.flatMap { 要么.纯(f(0)美元)}
  }
}

Scala-要么单子


包的单子

密封特性My要么[+E, +A] {
  def flatMap[EE >: E, B](f: A => My要么[EE, B]): My要么[EE, B] =
    单子(My要么 (EE, ?]].flatMap(这)(f)

  def map[EE >: E, B](f: A => B): My要么[EE, B] =
    单子(My要么 (EE, ?]].map () (f)
  
}
情况下类My左[E](E: E)扩展了My要么[E, Nothing]
情况下类My正确的[A](A: A)扩展了My要么[Nothing, A]

// ...

隐式def my要么单孢体[E] = 新 单孢体[My要么]; ?]] {
  def纯 [A](A: A) = My正确的(A)

  def flatMap[A, B](ma: My要么[E, A])(f: A => My要么[E, B]): My要么[E, B] = ma match {
    情况下 My左(a) => My左(a)
    情况下 My正确的(b) => f(b)
  }
}

还要注意,捕获异常很容易:您所要做的就是映射 to 正确的. (不过,为了简洁,我们在示例中没有这样做.)

未来的单子

让我们来看看我们需要的最后一个单子:未来单子. 未来 monad基本上是一个值的容器,该值要么现在可用,要么将在不久的将来可用. 你可以用 mapflatMap 它将等待未来值被解析,然后再执行依赖于首先被解析的值的下一段代码. 这与JS中的承诺s概念非常相似.

我们现在的设计目标是将不同语言的现有异步api连接到一个一致的基础上. 事实证明,最简单的设计方法是在构造器中使用回调.

而回调设计在JavaScript和其他语言中引入了回调地狱问题, 这对我们来说不是问题, 因为我们使用单子. 事实上, 承诺 对象——的基础 JavaScript对回调地狱的解决方案-是单子本身!

那么未来单子的构造函数呢? 上面有这样的签名:

构造函数 :: ((要么 err a -> void) -> void) -> 未来 (要么 err a)

我们把它分成几部分. 首先,让我们定义一下:

type 回调 = 要么 err a -> void

So 回调 是一个函数,要么是一个错误或一个解析值作为参数,并返回什么. 现在我们的签名是这样的:

构造函数 :: (回调 -> void) -> 未来 (要么 err a)

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

至于未来单子本身的设计,让我们看一下它的内部结构. 关键思想是有一个缓存变量,在未来单子被解析时保存一个值, 或者别的什么都没有. 你可以通过一个回调来订阅未来,如果value被解析,它将立即被触发, 如果不是, 会将回调放到订阅者列表中吗.

一旦未来解决了, 此列表中的每个回调将在单独的线程中使用解析值(或作为事件循环中执行的下一个函数)精确触发一次, 在JS的情况下.注意,小心地使用同步原语是至关重要的, 否则可能出现竞态条件.

基本流程是:启动作为构造函数参数提供的异步计算, 并将它的回调指向我们的内部回调方法. 同时,您可以订阅未来单子并将回调放到队列中. 计算完成后,内部回调方法调用队列中的所有回调. 如果你熟悉响应式扩展(RxJS, Rx斯威夫特等).),它们使用与异步处理非常相似的方法.

未来单子的公共API包括 , map, flatMap,就像前面的单子一样. 我们还需要一些方便的方法:

  1.  异步,它接受一个同步阻塞函数,并在一个单独的线程上执行它
  2. 遍历,它接受一个值数组和一个将值映射到a的函数 未来,并返回 未来 已解析值数组的

让我们看看结果如何:

JavaScript-未来单子


导入单孢体./单子”;
import {要么, 左, 正确的} from './ ';
导入{none, 一些}./选项”;

导出类未来扩展单孢体 {
  // 构造函数 :: ((要么 err a -> void) -> void) -> 未来 (要么 err a)
  构造函数(f) {
    超级();
    这.订阅用户= [];
    这.Cache = none;
    f(这.回调)
  }

  // callback :: 要么 err a -> void
  callback = (值) => {
    这.缓存= 新 一些(值)
    而(这.用户.长度){
      Const订阅者=此.用户.转变();
      订户(值)
    }
  }

  // subscribe :: (要么 err a -> void) -> void
  subscribe = (订阅者) => 
    (这.Cache ===无 ? 这.用户.Push(订阅者):订阅者(此).缓存.值))

  to承诺 = () => 新 承诺(
    (resolve, reject) =>
      这.订阅(val => val.is左 () ? 拒绝(val.值):解析(val.值))
  )

  // 纯 :: a -> 未来 a
  纯净=未来.纯

  // flatMap :: (a -> 未来 b) -> 未来 b
  flatMap = f =>
    新未来(
      cb => 这.订阅(value => value.is左 () ? Cb (值): f(值).值).订阅(cb))
    )
}

未来.异步 = (nodeFunction, ...args) => {
  返回 新未来(cb => 
    nodeFunction (...args, (err, data) => err ? cb(新 左(err)): cb(新 正确的(data)))
  );
}

未来.纯 = value => 新未来(cb => cb(要么.纯(值)))

// 遍历 :: [a] -> (a -> 未来 b) -> 未来 [b]
未来.遍历 = 列表 => f =>
  列表.减少(
    (acc, elem) => acc.flatMap(values => f(elem).地图(value => [...值,值])), 
    未来.纯([])
  )

Python-未来单子


从monad导入monad
从选项导入nil,一些
from either import either, 左, 正确的
从functools导入reduce
进口线程

类未来(单轴):
  # __init__ :: ((要么 err a -> void) -> void) -> 未来 (要么 err a)
  Def __init__(自我, f):
    自我.订阅者= []
    自我.缓存= nil
    自我.信号量=线程.Bounded信号量 (1)
    f(自我.回调)

  # 纯 :: a -> 未来 a
  @staticmethod
  def纯(价值):
    返回未来(lambda cb: cb.纯(值)))

  defexec (f, cb):
    试一试:
      数据= f()
      cb(右)(数据)
    Exception as err:
      cb(左(err))

  Def exec_on_thread(f, cb):
    穿线.线程(=未来的目标.执行,参数=[f, cb])
    t.开始 ()

  def异步(f):
    返回未来(lambda cb:未来.exec_on_thread (f, cb))

  # flat_map :: (a -> 未来 b) -> 未来 b
  Def flat_map (自我, f):
    返回未来(
      Lambda cb: 自我.订阅(
        Lambda值:cb(值) 如果(值).f(value . left.值).订阅(cb)
      )
    )

  # 遍历 :: [a] -> (a -> 未来 b) -> 未来 [b]
  def导线(加勒比海盗):
    返回lambda f:
      Lambda acc, elem: acc.flat_map (
        Lambda值:f(elem).地图(
          Lambda值:values + [value]
        )
      ), 加勒比海盗,未来.纯([]))

  # callback :: 要么 err a -> void
  Def callback(自我, 值):
    自我.信号量.获得()
    自我.缓存= 一些(值)
    而(len(自我.用户) > 0):
      Sub = 自我.用户.流行(0)
      穿线.线程(目标=子,arg游戏=(值))
      t.开始 ()
    自我.信号量.释放 ()
  
  # subscribe :: (要么 err a -> void) -> void
  Def 订阅(自我, 订阅者):
    自我.信号量.获得()
    如果(自我.缓存.定义):
      自我.信号量.释放 ()
      订户(自我.缓存.值)
    其他:
      自我.用户.追加(用户)
自我.信号量.释放 ()

Ruby-未来单子


require_relative”./单轴的
require_relative”./不是的
require_relative”./选项”

class 未来 < 单孢体
  Attr_accessor:订阅者,:缓存,:信号量

  # initialize :: ((要么 err a -> void) -> void) -> 未来 (要么 err a)
  def初始化(f)
    @订阅者= []
    @缓存= 没有美元
    @信号量 =队列.新
    @信号量.推动(零)
    f.调用(方法(回调))
  结束

  # 纯 :: a -> 未来 a
  def自我.纯(值)
    未来.新(-> (cb) { cb.电话(.纯(值))})
  结束

  def自我.异步(f * args)
    未来.新(-> (cb) {
      线程.新{
        开始
          cb.电话(右.新(f.电话(* args)))
        rescue => e
          cb.电话(左.新(e))
        结束  
      }
    })
  结束

  # 纯 :: a -> 未来 a
  def纯(值)
    自我.class.纯(值)
  结束

  # flat_map :: (a -> 未来 b) -> 未来 b
  def flat_map (f)
    未来.新(-> (cb) { 
      订阅(-> (值) {
        如果(值.is_left)
          cb.调用(值) 
        其他的
          f.调用(值.值).订阅(cb)
        结束
      }) 
    })
  结束

  # 遍历 :: [a] -> (a -> 未来 b) -> 未来 [b]
  def自我.遍历(加勒比海盗)
    加勒比海盗.减少(未来.Pure ([])) do |acc, elem|
      acc.flat_map (-> (values) {
        f.调用(elem).地图(-> (值) { values + [value] })
      })
    结束
  结束

  # callback :: 要么 err a -> void
  def回调(值)
    信号量.流行
    自我.缓存= 一些.新(值)
    而(用户.count > 0)
      Sub = 自我.用户.转变
      线程.新{
        sub.调用(值)
      }
    结束
    信号量.推动(零)
  结束
  
  # subscribe :: (要么 err a -> void) -> void
  def订阅(用户)
    信号量.流行
    如果(自我.缓存.定义)
      信号量.推动(零)
      订阅者.调用(缓存.值)
    其他的
      自我.用户.推动(用户)
      信号量.推动(零)
    结束
  结束
结束

斯威夫特-未来单子


进口的基础

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

class 未来 {
  typealias 回调 = (要么) -> Void
  
  var 用户: Array<回调> = Array<回调>()
  var 缓存: Maybe<要么> = .没有一个
  var 信号量 = Dispatch信号量(值为1)

  lazy var callback: callback = {value in
    自我.信号量.wait ()
    自我.缓存= .一些(值)
    当(自我.用户.count > 0) {
      让订阅者=自己.用户.流行Last ()
      background.异步{
        订阅者?(值)
      }
    }
    自我.信号量.信号()
  }
  
  init(_ f: @escaping (@escaping 回调) -> Void) {
    f(自我.回调)
  }
  
  函数c订阅(_ cb: @逃避回调){
    自我.信号量.wait ()
    切换缓存{
    情况下 .没有:
      用户.追加(cb)
      自我.信号量.信号()
    情况下 .一些(让价值):
      自我.信号量.信号()
      cb(值)
    }
  }
  
  static func 纯(_ value: B) -> 未来 {
    回到未来 { $0(要么.纯(值))}
  }
  
  func flatMap(_ f: @escaping (A) -> 未来) -> 未来 {
    回到未来 { [weak 自我] cb in
      Guard let 这 = 自我 其他的 {返回}
      这.中订阅{值
        开关值{
        情况下 .左(让犯错):
          cb(要么.左(err))
        情况下 .正确的(让x):
          f(x).订阅(cb)
        }
      }
    }
  }
  
  func map(_ f: @escaping (A) -> B) -> 未来 {
    回归自我.flatMap { 未来.纯(f(0)美元)}
  }
  
  static func 遍历(_ 列表: Array, _ f: @escaping (A) -> 未来) -> 未来> {
    返回列表.减少(未来>.纯(Array())) { (acc: 未来>, elem: A) in
      返回acc.flatMap{元素在
        返回f (elem).映射{val in
          返回元素+ [val]
        }
      }
    }
  }
}

Scala-未来单子


包的单子

导入java.跑龙套.并发.信号量

类My未来[A] {
  private var 用户: List[My要么[Exception, A] => Unit] = List()
  private var 缓存: MyOption[My要么[Exception, A]] = My没有一个
  private val 信号量 = 新 信号量 (1)

  def 这(f: (My要么[Exception, A] => Unit) => Unit) {
    这()
    f(这.回调_)
  }

  def flatMap[B](f: A => My未来[B]): My未来[B] =
    单子(My未来).flatMap(这)(f)

  def map[B](f: A => B): My未来[B] =
    单子(My未来).map () (f)

  def callback(value: myeeither [Exception, A]): Unit = {
    信号量.收购
    缓存= My一些(值)
    用户.foreach { sub =>
      = 新 线程 ()
        新 Runnable {
          def run: Unit = {
            子(值)
          }
        }
      )
      t.开始
    }
    订阅者 = List()
    信号量.释放
  }

  def 订阅(sub: My要么[Exception, A] => Unit): Unit = {
    信号量.收购
    缓存匹配{
      情况下 My没有一个 =>
        订阅者 = sub::订阅用户
        信号量.释放
      情况下 My一些(值) =>
        信号量.释放
        子(值)
    }
  }
}

对象My未来 {
  def 异步[B, C](f: B => C, arg: B): My未来[C] =
    新 My未来[C]({ cb =>
      = 新 线程 ()
        新 Runnable {
          def run: Unit = {
            尝试{
              cb(重现(f (arg)))
            } catch {
              情况下 e: Exception => cb(My左(e))
            }
          }
        }
      )
      t.开始
    })

  def 遍历[A, B](列表: List[A])(f: A => My未来[B]): My未来[List[B]] = {
    列表.fold正确的(单细胞生物(My未来).纯(List[B]())) { (elem, acc) =>
      单子(My未来).flatMap(acc) ({ values =>
        单子(My未来).地图(f(elem)) { value => value :: values }
      })
    }
  }
}

// ...

隐式val my未来单孢体 =新的单子(My未来) {
  def纯 [A](A: A): My未来[A] = 
    新 My未来[A]({ cb => cb(my要么单孢体[Exception].纯(a))})

  def flatMap[A, B](ma: My未来[A])(f: A => My未来[B]): My未来[B] =
    新 My未来[B]({ cb =>
      ma.订阅(_ match {)
        情况下 My左(e) => cb(My左(e))
        情况下 My正确的(a) => f(a).订阅(cb)
      })
    })
}

的公共API是如何 未来 不包含任何底层细节,比如线程,信号量,或者其他类似的东西. 你所需要的就是用回叫提供一些东西,就是这样!

从单子组成一个程序

好的,让我们试着用单子来做一个实际的程序. 假设我们有一个包含url列表的文件,我们希望并行地获取每个url. 然后,为了简洁起见,我们希望将每个响应缩减为200字节,并打印出结果.

我们首先将现有的语言api转换为一元接口(参见函数) readFile获取). 现在我们有了这个,我们可以把它们组合起来得到最终的结果. 请注意,链本身是超级安全的,因为所有的细节都包含在单子中.

javascript -样例单子程序


import {未来} from './未来”;
import {要么, 左, 正确的} from './ ';
从“文件”中导入{readFile};
从' HTTPS '导入HTTPS;

const getResponse = url => 
  新未来(cb => http.get(url, res => {
    Var body = ";
    res.on('data', data => body += data);
    res.on('结束', data => cb(新 正确的(body)));
    res.on('error', err => cb(新 左(err)))
  }))

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

未来
  .异步(readFile,资源/ url.txt”)
  .地图(data => data.to字符串 ().分割(“\ n”))
  .flatMap(urls => 未来.遍历(url) (getShortResponse))
.地图(控制台.日志)

python -样例单孢体程序


进口http.客户端
进口线程
导入的时间
进口操作系统
从未来进口
from either import either, 左, 正确的

Conn = HTTP.客户端.HTTPSConnection(“在.维基百科.org”)

def read_file_sync (uri):
  Base_dir =操作系统.路径.dirname(__file__) #<-- absolute dir the script is in
  路径= OS.路径.加入(base_dir uri)
  其中open(路径)为f:
    返回f.read ()

def 获取_sync (uri):
  康涅狄格州.请求(“得到”,uri)
  R = 康涅狄格州.getresponse ()
  返回r.read ().解码(“utf - 8”)(200):

def read_file (uri):
  回到未来.异步(λ:read_file_sync (uri))

def获取(uri):
  回到未来.异步(λ:获取_sync (uri))

def主要(args = 没有一个):
  Lines = read_file("../资源/ url.txt”).映射(lambda res: res.split行s ())
  内容=行.flat_map (lambda url:未来.遍历(url)(获取)
  输出=内容.Map (lambda res: print("\n").加入(res)))

如果__name__ == "__main__":
main ()

Ruby-Sample 单孢体程序


需要的./lib/future”
需要“net/http”
需要“uri”

信号量=队列.新

def读(uri)
  未来.异步(-> () { File.读(uri)})
结束

def获取(url)
  未来.异步(-> () {
    uri = uri (url)
    Net:: HTTP.get_response (uri).身体[0..200]
  })
结束

读(“资源/ url.txt”)
  .地图(-> (x) { x.分割(“\ n”)})
  .flat_map (-> (urls) {
    未来.遍历(urls, -> (url) { 获取(url) })
  })
  .地图(-> (res) { puts res; 信号量.推动(true)})

信号量.流行

斯威夫特-Sample 单孢体程序


进口的基础

enum Err:错误{
  一些(字符串)
}

func readFile(_ 路径: 字符串) -> 未来 {
  回到未来 { callback in
    background.异步{
      让url = url (fileURLWithPath:路径)
      Let text = try? 字符串(contentsOf: url)
      如果让res = text {
        callback(要么.纯(res))
      } 其他的 {
        callback(要么.左(犯错.一些(“读取url错误.txt”)))
      }
    }
  }
}

func 获取Url(_ url: 字符串) -> 未来 {
  回到未来 { callback in
    background.异步{
      让url = url(字符串:url)
      let 任务 = URLSession.共享.dataTask (: url!){(数据,响应,错误)in
        如果让err = error {
          callback(要么.左(err))
          返回
        }
        guard让nonEmptyData = data 其他的 {
          callback(要么.左(犯错.一些(“空响应”)))
          返回
        }
        guard let result = 字符串(data: nonEmptyData, encoding: 字符串).编码.Utf8) 其他的 {
          callback(要么.左(犯错.一些("无法解码响应"))
          返回
        }
        让index = result.指数(结果.开始Index, offsetBy: 200)
        callback(要么.纯(字符串(结果.. [字符串] in
      data.组件(separatedBy:“\ n”).filter {(行: 字符串) in !行.isEmpty}
    }.flatMap {url在
        回到未来.遍历(url) {url在
            返回获取Url (url)
        }
    }.映射{响应
      打印(反应)
    }

RunLoop.main.run ()

scala -样例单子程序


导入scala.io.Source
导入java.跑龙套.并发.信号量
进口单子._

扩展App {
  val 信号量 = 新 信号量 (0)

  def readFile(name: 字符串): My未来[List[字符串]] =
    My未来.异步[字符串, List[字符串]](filename => Source.fromResource(文件名).get行.toList的名字)
  
  def 获取(url: 字符串): My未来[字符串] =
    My未来.异步(字符串,字符串)(
      uri => Source.fromURL (uri).mk字符串.substring (0, 200),
      url
    )
  
  Val future = for {
    urls <- readFile("urls.txt”)
    entries <- My未来.遍历(url) (_)
  } yield { 
    println(条目)
    信号量.释放
  }

  信号量.收购
}

这就是实践中的it-monad解决方案. 您可以找到一个包含本文所有代码的repo GitHub上.

开销:完成. 好处:正在进行

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

了解基本知识

  • 单子的含义/定义是什么?

    monad是一个抽象接口,它定义了“纯”和“flatMap”函数. Pure允许您将普通值转换为一元值. FlatMap允许将普通参数函数应用于一元参数.

  • 单子是用来干什么的?

    单子在函数式编程中被广泛使用. 他们的主要目标是在一个地方隔离任何不可靠和危险的副作用, 因此,您可以在应用程序的所有其他部分享受安全编程.

  • 什么是单子类别?

    从技术上讲,单子是一个内函子,意思是它把一个类别映射到它自己. 但是你可以把那个函子的像看作一个具有一元副作用的新范畴.

  • 什么是单子编程?

    单元编程是一种将不同的单元值组合成一个大单元的技术. 之后就很容易处理所有的副作用了, 因为它们集中在一个单子上, 而不是很多单子.

聘请Toptal这方面的专家.
现在雇佣

Alexey Karasev

<路径 d="m2.3438,5.6562l-2.3438,2.3438,2.3438,2.3438v3.3137h3.3137l2.3425,2.3425,2.3425-2.3425h3.315v-3.315l2.3425-2.3425-2.3425-2.3425v-3.3137h-3.3138l-2.3437-2.3438-2.3438,2.3438h-3.3125v3.3125Zm5.0488,2.7528l2.754-2.7654.9705.9739-3.7245,3.7399-.9705-.9739-1.3672-1.3733.9705-.9752,1.3672,1.3739Z" fill="currentColor"><路径 class="text-white dark:text-transparent" clip-rule="evenodd" d="M10.1465 5.64374L7.39254 8.4091L6.02535 7.03518L5.05485 8.01039L6.42204 9.38364L7.39254 10.3575L11.117 6.61761L10.1465 5.64374Z" fill="currentColor" fill-rule="evenodd">验证专家 在工程
<路径 clip-rule="evenodd" d="M12.785 3.30422L8.19592 0.680947C7.8317 0.472777 7.41948 0.363281 7 0.363281C6.58052 0.363281 6.1683 0.472777 5.80408 0.680947L1.21446 3.30422C0.463414 3.73353 0 4.53222 0 5.3977V10.6014C2.84061e-05 11.0265 0.112371 11.444 0.325646 11.8116C0.538921 12.1793 0.845549 12.484 1.21446 12.6949L5.80408 15.3182C6.16837 15.5265 6.5807 15.636 7.00029 15.636C7.41987 15.636 7.8322 15.5265 8.19649 15.3182L12.785 12.6949C13.154 12.4841 13.4607 12.1794 13.6741 11.8117C13.8875 11.4441 13.9999 11.0265 14 10.6014V5.3977C13.9999 4.97258 13.8875 4.55504 13.6741 4.1874C13.4607 3.81975 13.154 3.51507 12.785 3.30422ZM5.09524 8.68011L4.60062 11.3333L7.19048 10.0807L9.78033 11.3333L9.28571 8.68011L11.381 6.80108L8.4854 6.41398L7.19048 4L5.89555 6.41398L3 6.80108L5.09524 8.68011Z" fill="currentColor" fill-rule="evenodd">15 的经验

莫斯科,俄罗斯

2017年6月26日成为会员

作者简介

Alexey (MEcon)精通几种语言,更喜欢函数式编程, 尤其是Scala, 为了减少浪费在捉虫子上的时间.

<路径 clip-rule="evenodd" d="M32.2841 4.61667C31.5969 3.86366 30.7379 3.4762 29.729 3.4762C28.7567 3.4762 27.7953 4.15609 27.1995 4.82502L27.2032 3.57124L25.3901 3.56758L25.3682 13.8574V13.9926L27.1922 13.9963L27.1995 10.7796C27.7515 11.3973 28.6214 11.7592 29.5901 11.7629C30.6575 11.7629 31.553 11.3827 32.2548 10.6334C32.964 9.88037 33.3222 8.81667 33.3259 7.59578C33.3295 6.3493 32.9786 5.34774 32.2841 4.61667ZM29.294 5.17228C29.9118 5.17228 30.4345 5.40622 30.8475 5.86314C31.2716 6.30544 31.4909 6.88664 31.4872 7.59212C31.4836 8.30126 31.2752 8.93729 30.8402 9.39787C30.4235 9.84016 29.9045 10.0668 29.2867 10.0668C28.6799 10.0668 28.1718 9.84382 27.7332 9.39421C27.3018 8.92998 27.0898 8.29395 27.0898 7.58481C27.0935 6.88298 27.3092 6.30178 27.7368 5.85583C28.1682 5.39526 28.6763 5.17228 29.2867 5.17228H29.294Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M34.2507 5.10654L34.2433 9.4491C34.2397 10.3849 34.4992 11.0538 35.0037 11.434C35.318 11.6716 35.7201 11.7922 36.1953 11.7922C36.5608 11.7922 37.1347 11.646 37.1347 11.646L36.9702 10.0632C36.9702 10.0632 36.5535 10.1984 36.3488 10.0522C36.1551 9.91333 36.0601 9.6209 36.0601 9.17495L36.0674 5.10654L37.7561 5.11019L37.7598 3.60053L36.071 3.59688L36.0747 1.42925L34.258 1.4256L34.2543 3.59322L32.7264 3.58957L34.2507 5.10654Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M19.3149 1.31226H10.341L10.3373 3.10703H13.9232L13.9086 11.4997V11.6642L15.8058 11.6679L15.7984 3.11069L19.3149 3.10703V1.31226Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M9.65013 5.90338L3.78329 0.0255737L2.41619 1.38171L4.41201 3.38119L0.0328979 7.74567L5.92167 13.6418L7.26684 12.3002L5.25274 10.2825L9.65013 5.90338ZM4.03551 8.7911C3.9953 8.80207 3.95509 8.80207 3.91488 8.7911C3.87467 8.78014 3.84178 8.76186 3.76867 8.68875L2.97546 7.89189C2.906 7.82244 2.88407 7.78588 2.87311 7.74567C2.86214 7.70546 2.86214 7.66526 2.87311 7.62505C2.88407 7.58484 2.90235 7.55194 2.97546 7.47883L5.47572 4.98223C5.54517 4.91278 5.58172 4.89085 5.62193 4.88353C5.66214 4.87257 5.70235 4.87257 5.74256 4.88353C5.78277 4.8945 5.81567 4.91278 5.88877 4.98588L6.68198 5.78275C6.75144 5.8522 6.77337 5.88876 6.78068 5.92896C6.79164 5.96917 6.79164 6.00938 6.78068 6.04959C6.76971 6.0898 6.75144 6.1227 6.67833 6.1958L4.17807 8.69241C4.10862 8.76186 4.07572 8.77648 4.03551 8.7911Z" fill="#204ECF" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M23.5259 4.66425C22.7838 3.89662 21.7713 3.4726 20.7332 3.50185C19.6841 3.50185 18.7593 3.87104 17.988 4.59845C17.2057 5.33318 16.8073 6.34936 16.8073 7.62508C16.8073 8.9008 17.2021 9.91699 17.9807 10.6554C18.7556 11.3864 19.6804 11.7593 20.7295 11.7629C20.7697 11.7666 20.8099 11.7666 20.8501 11.7666C21.8298 11.7666 22.8021 11.3462 23.5185 10.6152C24.0413 10.0778 24.6663 9.12743 24.67 7.64336C24.6736 6.15929 24.0486 5.20524 23.5259 4.66425ZM22.952 7.6397C22.9483 8.37808 22.7326 8.98853 22.3013 9.45641C21.8736 9.92064 21.3399 10.1546 20.7185 10.1546C20.1081 10.1509 19.5781 9.91699 19.1504 9.4491C18.7337 8.99218 18.518 8.38174 18.518 7.63239C18.518 6.88304 18.7337 6.2726 19.154 5.81568C19.5817 5.35145 20.1118 5.11751 20.7222 5.11751C21.3436 5.11751 21.8773 5.35511 22.305 5.82299C22.7363 6.28722 22.9556 6.89767 22.952 7.6397Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M39.8287 6.3859C39.8287 5.92167 40.07 4.90549 41.4115 4.90549C41.8867 4.90549 42.1572 5.06632 42.3838 5.2564C42.6032 5.43917 42.7128 5.88878 42.7128 6.26162V6.43708L40.4977 6.78434C38.7431 7.05118 37.7379 7.99426 37.7306 9.37598C37.7269 10.0924 37.9901 10.7065 38.4836 11.1561C38.9734 11.5984 39.6387 11.836 40.4282 11.836C41.4225 11.836 42.1682 11.4778 42.7019 10.7394C42.7055 11.0538 42.7055 11.3682 42.7055 11.6606H44.5515L44.5624 6.35301C44.5661 5.48669 44.281 4.64961 43.7253 4.18904C43.1734 3.73577 42.4533 3.50183 41.4115 3.50183H41.3969C40.6 3.50183 39.7556 3.71384 39.1891 4.20366C38.5201 4.7812 38.1911 5.6987 38.1911 6.40784L39.8287 6.3859ZM40.6658 10.246C40.0115 10.2423 39.5838 9.86946 39.5875 9.29557C39.5911 8.69974 40.0042 8.35248 40.8924 8.20261L42.7128 7.89557V7.91384C42.7092 9.28825 41.8684 10.246 40.6731 10.246H40.6658V10.246Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M45.4397 1.43286L45.425 11.6057V11.6532L47.059 11.6569L47.0773 1.48038V1.43652L45.4397 1.43286Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M48.5284 10.436C48.5284 10.0997 48.6454 9.8073 48.8867 9.56604C49.1279 9.32479 49.4167 9.20416 49.7493 9.20416C50.0856 9.20416 50.3817 9.32479 50.6193 9.56604C50.8606 9.8073 50.9812 10.0961 50.9812 10.436C50.9812 10.7796 50.8606 11.0647 50.6193 11.2987C50.3781 11.5399 50.0893 11.6569 49.7493 11.6569C49.4094 11.6569 49.1206 11.5399 48.8867 11.2987C48.6491 11.0647 48.5284 10.7796 48.5284 10.436ZM48.7331 10.436C48.7331 10.7211 48.8282 10.966 49.0292 11.1707C49.2266 11.3681 49.4679 11.4705 49.7566 11.4705C50.0418 11.4705 50.283 11.3681 50.4804 11.1707C50.6778 10.9697 50.7801 10.7284 50.7801 10.436C50.7801 10.1472 50.6778 9.90599 50.4804 9.7086C50.283 9.50756 50.0418 9.40521 49.7566 9.40521C49.4715 9.40521 49.2303 9.50756 49.0329 9.7086C48.8318 9.90599 48.7331 10.1472 48.7331 10.436ZM50.0454 11.1049L49.7164 10.5201H49.4935V11.1049H49.2924V9.76343H49.8371C50.1076 9.76343 50.2684 9.92427 50.2684 10.1436C50.2684 10.33 50.1295 10.4726 49.9394 10.5091L50.2721 11.1013H50.0454V11.1049ZM49.4935 9.94254V10.3519H49.8078C49.9833 10.3519 50.0673 10.2861 50.0673 10.1509C50.0673 10.012 49.9796 9.94254 49.8078 9.94254H49.4935Z" fill="#262D3D" fill-rule="evenodd">作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

Toptal开发者

加入总冠军® 社区.