http指南单子版_了解单子。 困惑的指南
http指南单子版
重要要点
- 避免显式处理有状态值是值得的。
- 使用monad,可以从代码中删除对有状态值的显式处理。
- 为了符合Monad类型,类型必须具有与之关联的特定类型的功能(称为“绑定”)。
- 使用monad的bind函数,有状态值会从一个monad传递到另一个monad,始终留在monad内部(而不是显式处理)。
- 使用monad可以解决许多不同类型的问题。
随着当前函数编程的爆炸式增长,“ monad”函数结构再次使新人心生恐惧。 莫纳德人起源于数学中的范畴论领域,并于1990年代被引入编程语言,它是诸如Haskell之类的纯函数语言的基本结构。
以下是大多数新手对单子的了解:
- 一个monad对进行输入和输出很有用。
- monad除输入和输出外,还可以用于其他方面。
- 一个monad很难理解,因为有关monads的大多数文章都讲得太多或太少。
第三个项目符号激励我写这篇文章-这篇向读者介绍单子词的文章(甚至是千篇)。 运气好的话,您将在这篇文章的结尾处感到单子并不那么可怕。
系统状态
在计算机程序中,“状态”一词描述了全局变量,输入,输出以及特定功能以外的任何其他内容。 关于程序状态,需要记住以下几点:
- 它的值从一个功能的执行一直持续到另一个功能。
- 它可用于多个功能。
- 它可以是可变的。 如果是这样,则其值与时间有关。
状态很难管理,因为没有单个函数拥有状态。 考虑这种情况:
- 功能编号1从状态中获取一个值,并在执行其指令时使用该值。 同时,功能编号2修改了该值。 结果,功能编号1根据最新的值做出决策。
- 更糟的是,功能编号1使用其过时的数据执行计算,然后将状态值替换为新的不准确的值。 现在,功能编号1的错误已变得具有传染性,从功能内部传播到整个全局系统。
无论如何,系统状态是时间的函数,所以时间是我们必须担心的额外维度。 我们真的不能问“ x
的值是多少?” 相反,我们必须问“在时间t
处x
的值是多少?”。 这增加了复杂性,使代码难以推理。 所以最重要的是...
表情和动作
表达式是具有值的一段文本。 例如,考虑以下代码:
x = 5
y = x + 7
x = y + 1
第一次出现的x
是具有值5的最后出现的表达式x
是与该代码包含其他表达式的值13的表达式。 例如,在中间一行, x + 7
是一个值为12的表达式。
在大多数计算机语言中,从键盘读取的命令是一个表达式,并且该表达式具有值。 考虑以下语句:
x = nextInput()
您会在Java,C ++和许多其他编程语言中找到这种语句。 现在,假设用户键入数字5
。 然后nextInput()
是一个值为5
的表达式。 语句的执行将nextInput()
表达式的值(值5
)分配给变量x
。 如果x
是程序状态的一部分,则此语句修改该状态。 正如我们已经看到的那样,修改系统状态可能很危险。
在我们的nextInput()
例如,值nextInput()
是依赖于时间的。 第一次执行nextInput()
, nextInput()
表达式的值可能为5。稍后,当您第二次执行nextInput()
时,相同表达式的值可能为17。在松散类型的语言中, nextInput()
可能从5
更改为"Hello, world"
。
为了消除时间依赖性,我们停止使用nextInput()
并将其替换为我将称为doInput
的函数。 作为表达式, doInput()
值不是5
或17
或"Hello, world"
。 相反, doInput()
的值是一个操作。 此操作在运行时从键盘获取输入。
一个动作可能会或可能不会发生。 从键盘读取是一个动作。 在屏幕上写"Hello, world"
是一个动作。 打开在"/Users/barry/myfile.txt"
找到的文件是一项操作。 建立与http://www.infoq.com
的连接是一项操作。 动作是某种计算。 通常,动作的详细信息不会在程序的源代码中详细说明。 相反,动作是运行时现象。
在许多语言中,当您考虑类型时,就会想到整数,浮点数,字符串,布尔值以及其他类似的东西。 您可能不会将动作视为一种类型。 但是,当我们执行monadic I / O时,诸如doInput()
之类的表达式的值就是一个动作。 doInput()
从doInput()
调用返回的值是动作类型。
用这种思维方式, doInput()
的值与时间无关。 无论表达式doInput()
在程序中出现的位置如何,该表达式始终具有相同的值。 它的值是从键盘获得输入的动作。
无论是州还是州,我们都取得了进步。
您不能用动作值做很多事情
我们有一个小问题。 我们想对一个值是一个动作的表达式做一些有用的事情。 我们该怎么做? 如果某个动作从键盘上获得数字5,则无法将1加到该动作上。
x = doInput()
print(x + 1)
在与语言无关的代码中,变量x
表示动作。 动作是一种类型,值1
是完全不同的类型。 因此,表达式x + 1
仅比在重新启动计算机的操作中添加1
有意义。 表达式restart + 1
是胡说!
如果您不想在用户输入中加1
,该怎么办? 现在下面的代码有意义吗?
x = doInput()
print(x)
不,不是。 语句x = doInput()
为x
分配了一个动作,因此语句print(x)
试图显示一个动作。 当您尝试在计算机屏幕上显示动作时,它看起来像什么?
您可能会争辩说,使用弱类型语言,您可以模糊输入操作和它们从键盘获得的值之间的差异。 就像2 == "2"
在JavaScript中是正确的一样, 5 == the_action_obtaining_5
在某些弱类型函数语言中也可能是正确的。 但是在the_action_obtaining_5
需要某种处理才能从the_action_obtaining_5
获得5
。 此外,如果您失去了5
和the_action_obtaining_5
之间的区别,那么doInput()
和原始的nextInput()
之间几乎没有什么区别; 返回到以输入函数调用为时间依赖表达式的地方。 那不是你想要的地方。
建立一条链
因此,情况很明显; 我们无法显示doInput()
表达式的值。 但是,如果我们可以巧妙地链接动作并在doInput()
动作之后执行另一个要打印的动作,该怎么办? 亲爱的读者,那将是一个单子。
doInput()
操作与诸如5
(用户在键盘上输入的值)之类的值有关。 当我们处理doInput()
,我们最感兴趣的是用户键入的数字5
,而不是doInput()
操作本身。
让我们仔细看看。 如果程序跟踪仓库中的库存,则输入数字5可能表示货架上的箱子数。 值5与问题域有关。 如果您走到管理仓库的人那里并说“您有5个盒子”,则该人会明白您的意思。 另一方面, doInput()
表达式的值是对库存管理人员没有意义的动作。 doInput()
操作是我们处理库存方式的doInput()
。 因此,在本文中,我将与动作相关的相关值与动作本身区分开来。 为了强调动作缺乏针对性,我将动作本身称为工件 。
回顾一下。 当您执行doInput()
并且用户输入数字5时,
- 接收输入的动作就是我们所说的工件 。
- 数字5是我们称为相关值的数字 。
这种相关的/伪造的术语无法与每个monad很好地配合使用,但是它可以帮助我解释monad的全部含义。 为了提供更多帮助,在本文的大多数示例中,我都将相关值设为整数值。
如果我很草率,我认为doInput()
动作是一个具有相关值的容器,例如内部有5
冒泡。 容器隐喻很有用,因为一旦将值与monad相关联,我们就希望将该值附加到monad上。 我们不喜欢让价值从其monad容器中泄漏出去。 请记住,当您认真对待Monad时,这些相关性和容器类比会崩溃。 即使这样,这些类比对于建立有关单子的直觉还是非常有用的。
因此,这是我们面临的挑战:我们必须形式化使用与doInput()
操作相关的相关用户输入的方式。 (在本文中,“正式化”一词的意思不是“绝对严格。”而是“用简单的英语简要描述来使内容更精确”。)
有些类型是单子。 其他不是
在典型的编程语言中,您可能具有整数类型,浮点类型,布尔类型,字符串类型以及许多不同类型的复合类型。 您可以将一种类型描述为单子类型,也可以将其描述为单子类型。 monad类型是哪种类型?
对于初学者,单子类型具有相关的值或与之关联的许多相关值。 以下是一些具有相关值的类型的示例:
- I / O操作类型:
对于键盘输入操作,相关值是用户输入的值。 工件是动作本身。 - 列表类型:
想象一个值列表:[3,17,24,0,1]。 在此列表中,相关的值为3、17、24、0和1。工件是事实,这些值被收集以形成一个列表。 如果您不喜欢列表,可以考虑数组,向量或您喜欢的编程语言中的任何其他集合结构。 - 也许类型:
空值在许多语言中都是有问题的,并且函数式编程有一种解决方法。 在我的简化方案中,Maybe值包含数字(例如5)或Nothing指示符。 如果计算无法确定值,则可能包含Nothing而不是值。Java和Swift之类的语言具有Optional类型。 Optional与我们的Maybe类型相似。
对于产生Maybe值的计算,相关值可以是数字或特殊的Nothing指示符。 工件是一个概念,即计算的结果不仅仅是一个数字。
- 作家类型:
Writer是一项功能,作为其工作的一部分,它会生成一些写入到正在进行的日志中的信息。 想象一下,例如,一个带有附加值的花式平方函数:square(6) = (36, "false")
数字
36
是6*6
。 单词"false"
表示答案36
不能被10整除。在应用了不同Writer函数的多个应用程序之后,日志可能看起来像这样:"false true false"
在此示例中,相关值是数字结果,例如
36
。 工件是字符串值("true"
或"false"
),并将数字结果与字符串值捆绑在一起。
对于每种类型,正式使用该类型的相关值会带来一些挑战。 特别是:
- 对于“ I / O操作”类型:
您有一个动作,并且某些用户输入与此动作相关联。 您不能只为该操作加1,也不能在计算机屏幕上回显该操作。 动作类型不正确。 您必须定义如何使用动作的相关值来做某事。 - 对于列表类型:
你有一个带有数字的清单。 在我们的示例中,其中包含3、17、24、0和1。 您不能对列表本身进行数值计算。 列表的类型不正确。 您必须定义如何使用列表中的每个数字来执行某项操作(例如“ +1”)。 - 对于Maybe类型:
想象两个名为maybe1
和maybe2
Maybe
值。maybe1
值Nothing
关联,而maybe2
值有5
关联。maybe1
值来自某个不成功的计算,可能的maybe2
值来自某个计算,其中5
是结果。你可以添加
1
至maybe1
价值? 号的Nothing
值与相关联的maybe1
,所以1 + maybe1
是无意义的。可以将
maybe2
值加1
吗? 为了了解单子,我的答案仍然是“否”。5
值与maybe2
相关联,但maybe2
与5
不相同。maybe2
值不是一个数字-这是一个与5
关联的构件结构。 所以1 + maybe2
是没有意义的。缺少的链接是,您必须定义如何使用与
Maybe
值关联的相关值来做某事。 - 对于Writer类型:
从一些帮助您找到((6 * 6)+ 4)/ 5的普通函数开始。square(6) = 36
plus4(36) = 40
dividedBy5(40) = 8您可以将这些功能链接在一起以获得所需的结果:
dividedBy5(plus4(square(6))) = 8
但是,如果每个函数都是一个Writer,并且每个函数的结果都包含一个可被10整除的指标,那么情况就不一样了:
累积的日志包含:
square(6) = (36, "false") "false"
plus4(36) = (40, "true") "false true"
dividedBy5(40) = ( 8, "false") "false true false"
您不能将plus4
函数应用于square
函数返回的对。
plus4(square(6))
是
plus4((36, "false")
这是没有意义的
取而代之的是,您将plus4
函数应用于square
函数执行中的相关值。 同样,在应用dividedBy5
函数从相关值plus4
功能的执行。
使用一个简单的规则,您可以一次全部确定形式,一种将每个函数调用应用于上一个调用的相关值的方式。 这使我们进入了bind
功能。
要使类型成为Monad,它必须具有绑定功能
在大多数编程语言中,类型都有自己的功能。 例如,整数类型具有+,-,*和/函数。 字符串类型具有其串联功能。 布尔类型具有其or,and和not函数。
要成为monad,一个类型必须具有使用monad的相关值或多个值的函数,并且该函数必须具有特定的形式。 让我们看看这种形式是什么。
函数形式(错误观念)的第一个候选对象是函数(我将其称为badIdea
),用于将相关值与monad隔离。 例如, badIdea
函数可能会从操作中获取用户的输入。 如果调用doInput()
且用户键入数字5
,则badIdea(doInput())
为5
。 应用badIdea
函数后,可以打印badIdea()
调用的结果值。 您甚至可以将1
添加到badIdea()
调用的结果值中。
x = badIdea(doInput())
print(x)
y = x + 1
现在您回到了开始的地方,然后提出了nextInput()
函数的时间依赖性问题。 表达式badIdea(doInput())
具有原始nextInput()
函数的所有不良功能。 一次执行badIdea(doInput())
,作为表达式,其值可能为5
。 badIdea(doInput())
执行badIdea(doInput())
,其值可能变为17
。 在松散类型的语言中, badIdea(doInput())
可能从5
变为"Hello, world"
。
使用badIdea
函数,您可以从doInput()
操作中获取相关值,并使用该值执行任何所需的操作。 让我们从doInput()
操作中获取相关的值, 然后创建另一个使用该值执行有用操作的操作 ,而不是实施这个坏主意。 这很重要:您在doInput()
操作之后执行另一个操作。 当您将此思想形式化时,动作类型将变为单子类型。 因此,让我们开始正式化想法:
我们从两件事开始:一个动作和一个功能。 例如,
- 动作
doInput()
从键盘获取一个数字。
- 函数
doPrint
接受一个数字,并产生一个在屏幕上写入该数字的动作。
doPrint(5)
=在屏幕上写入5的动作
doPrint ( 19)
=在屏幕上写入19的动作
图1说明了这种情况。 在此图中,每个齿轮图代表某种动作
![](/assets/blank.gif)
我可以将doPrint
描述为从数量到动作的函数。 当您执行函数式编程时,此类函数自然会出现。
让我们在这里暂停一下我的说法; 考虑两个表达式,如opSystem
没有括号,并opSystem()
用括号。 opSystem
表达式的值是一个特定的函数-该函数可以伸出并发现当前正在运行的操作系统。 但是opSystem()
表达式的值是一个名称-类似于"Linux"
或"Windows 10"
。 简而言之,
opSystem
代表功能,并且opSystem()
代表从调用opSystem
函数返回的值。
考虑到这一点,请注意带括号的“ action doInput()
”与不带括号的“ doPrint
函数”之间的区别。 doInput
和doPrint
函数均返回值,并且在两种情况下,这些值均为操作。 当我写“ action doInput()
”时,我指的是doInput()
函数调用返回的doInput()
。 当我写“函数doPrint
”时,我指的是doPrint
函数本身,而不是函数的返回值。 这说明了我在图1中说明doInput()
和doPrint
的方式上的差异。为了说明doInput()
,我画了一个齿轮,该齿轮应该使您想起一个动作。 为了说明doPrint
,我画了一个箭头,它使您想起一个功能。 当您在考虑单子时,这有助于使此类问题始终处于您的思考重点。
通过doInput()
动作和doPrint
函数,我们还需要一件来制作单子。 我们需要一种将doInput()
和doPrint
在一起的方法。 更准确地说,我们需要一个公式来执行任何动作A
,以及任何从数到动作函数f
,并将它们组合以创建一个新动作。 我们将此公式命名为bind
。 见图2。
![](/assets/blank.gif)
在上一段中,我称bind
为公式,但bind
实际上是另一个函数。
bind(A,f)
=某种动作
bind
函数是一个高阶函数,因为bind
函数将一个函数作为其参数之一。 如果您不习惯考虑函数的功能,则bind函数会使您的头脑混乱。
对于输入和输出, bind
函数必须是一条通用规则,该规则将任何I / O操作A
和任何从编号到操作功能f
用作参数。 bind
函数必须返回一个新的I / O操作。 例如:
doInput()
=从键盘获取数字的动作
doPrint(x)
=在屏幕上写入x
值的动作
bind(doInput(),doPrint)
=
在屏幕上写入doInput()
相关值的操作
参见图3。
![](/assets/blank.gif)
让我们稍微更改一下示例:
- 令
doPrintPlus1(x)
=在屏幕上写入x+1
值的动作bind(doInput(),doPrintPlus1)
=
在屏幕上写入(doInput()
相关值)+1的动作
参见图4。
![](/assets/blank.gif)
通常,对于输入/输出动作A
以及从数量到动作函数f
:
bind(A,f) =
将f
应用于A
的相关值的动作
参见图5。
![](/assets/blank.gif)
如果您发现bind
的描述令人困惑,那么您并不孤单。 高阶函数不容易理解。
在Haskell编程语言中, bind
函数起着如此重要的作用,在该语言中内置了bind
运算符>>=
。 实际上,许多用于处理monad的功能都直接引入了Haskell。
不再依赖时间
让我们重新访问图3所示的doInput(),doPrint场景。
bind(doInput(),doPrint)
=
在屏幕上写入doInput()
相关值的操作
bind(doInput(),doPrint)
表达式的任何部分bind(doInput(),doPrint)
具有与时间相关的值。 无论此表达式何时出现在您的代码中,
doInput()
始终是相同的动作-从键盘获取数字的动作。doPrint
始终是相同的从编号到动作功能。- 整个表达式
bind(doInput(),doPrint)
始终是相同的动作-将数字写入屏幕的动作。
用户在键盘上键入的值从一瞬间变为下一瞬间,但是bind(doInput(),doPrint)
表达式的任何部分都不代表该值。 我们还没有消除所有副作用,但是我们已经消除了代码中对系统状态的任何明确提及。 用户在键盘上输入数字后,该数字就像烫手山芋一样从一个动作传递到另一个动作。 代码的变量均不代表用户键入的值。
并非总是与数字和输入/输出操作有关
在到目前为止的示例中,
doInput()
的相关值是一个数字,而doPrint
的参数类型也是一个数字。doInput()
的类型是一个动作,而doPrint
的返回类型是一个动作。
bind
函数告诉您如何组合doInput()
和doPrint
以获得全新的操作。
有些单子与数字或输入/输出操作没有任何关系。 因此,让我们以稍微更一般的方式doPrint
对doInput
, doPrint
和bind
函数的观察:
doPrint
的参数类型与doInput()
的相关值类型相同。doPrint
的返回类型与doInput()
的类型相同。
bind
函数告诉您如何组合doInput()
和doPrint
以获得全新的价值。 该新值与原始doInput()
值的类型相同。
更多单子类型
任何具有绑定函数(以及我将在本文末尾描述的其他函数)的类型都是monad。 通过前面几节中介绍的输入/输出绑定函数,输入/输出操作类型变为monad。 我们之前介绍的列表,Maybe和Writer类型也是monad类型。 这是列表类型的工作方式:
从两件事开始:一个列表L
和一个函数f
。 同样,函数f
是某种类型的。
- 作为参数,
f
接受一个值,该值的类型与列表中的元素相同。 - 对于其结果,
f
产生一个列表。
如果列表包含数字,则f
是从数字到列表的函数。 这是一个公式( bind
的定义),该公式采用任何列表和任何从编号到列表的功能,并将它们组合以创建新列表:
bind(L,f)
=
通过将f
应用于每个列表而获得的新列表
L
的元素,然后展平结果列表列表
让我们看一个例子:
- 令
f(x)
=列表[squareRoot(x),-squareRoot(x)]
根据需要,
f
是一个从数字到列表的函数。令
L = [4,25,81]
根据我们对列表
bind
的定义:bind(L,f)
=
[[2,-2], [5,-5], [9,-9]]
= [2,-2,5,-5,9,-9]
bind
函数为您提供了将函数f
应用于列表L
任何元素的所有可能结果。 参见图6。
![](/assets/blank.gif)
- 这是一个不涉及数字的示例。 令
x
为一本书,令f(x)
=该书的作者列表例如,
f(C_Programming_Lang) = [Kernighan,Ritchie]
在此示例中,
f
是从书到表的功能。 根据我们对列表bind
的定义:bind([C_Programming_Lang,
Design_Patterns], f) =
扁平化
[[Kernighan,Ritchie],[Gamma,Helm,Johnson,Vlissides]] =
[Kernighan,Ritchie,Gamma,Helm,Johnson,Vlissides]
我们已经设法从书籍清单中获得了作者名单。 参见图7。
![](/assets/blank.gif)
注意图2至图7之间的相似之处:
- 在图的左侧,您有一个monad值(例如,一个动作,一个列表)和一个函数。 该函数采用相关值并产生单值。
- 左中角有一个箭头,指示
bind
功能的应用。 - 在图的右侧,您具有全新的monad值。
Maybe类型的bind
函数如何? 令m
为Maybe值(包含数字或Nothing),令f
为接受数字并产生Maybe值的函数。 bind(m,f)
的值取决于Maybe值内的内容:
bind(m,f)
=要么
f(m's pertinent value)
如果m
不包含任何内容
要么
如果m
包含任何内容
让我们列出一些例子。
让maybe1
包含任何东西
让maybe2
包含5
让maybe3
包含0
令f(x)
=可能包含100 / x
值
然后
bind(maybe1,f) =
可能不包含Nothing
值
bind(maybe2,f) =
可能包含20的值
bind(maybe3,f) =
可能不包含Nothing
值
同样, bind
函数的参数是monad(在这种情况下为Maybe值)和函数。 该函数的参数是monad的相关值,并且该函数返回另一个monad。
当然,我们可以为Writer monad创建一个bind
函数。
bind((aNumber,aString), f)
=
(
f(aNumber), aString+
的数字部分f(aNumber), aString+
的字符串部分f(aNumber))
一些示例可以帮助您理解这一点。 回忆我们的square
, plus4
和dividedBy5
函数-这些函数的返回值包括一个10整除指标。
bind((36, "false"), plus4) = (40, "false true")
参见图8。
bind((40, "false true"), dividedBy5) = (8, "false true false")
当您应用bind
,结果的字符串部分包括来自先前计算的字符串。 字符串部分是所有计算的运行日志。
![](/assets/blank.gif)
Monad需要一项附加功能
bind
函数可能不容易理解,但是monad必须具有的另一个函数非常简单。 我称它为toMonad
函数。
toMonad
函数将相关值作为其参数。toMonad
函数返回一个monad。
粗略地说, toMonad
返回最简单的monad,它可能包含给定的相关值。 (有一个涉及toMonad
与bind
交互的更精确的定义,但我不会赘述。如果您很好奇,请访问https://en.wikibooks.org/wiki/Haskell/Understanding_monads。)
- 对于I / O Action monad,
toMonad(aValue)
=一个相关的值为aValue
但其作用是什么都不做的动作。 - 对于列表monad,
toMonad(aValue)
=包含aValue
作为其唯一条目的列表。 参见图9。 - 对于Maybe单
toMonad(number)
,toMonad(number)
=包含该number
的Maybe值请记住,包含5的Maybe值与普通的旧数字5并不完全相同。
- 对于Writer monad,
toMonad
返回包含空字符串的monad。toMonad(number) = (number, "")
而已!
monad是具有bind
函数和toMonad
函数的类型。 bind
功能从monad机械地移动到monad,而没有显式揭示monad的相关值。
从功能上考虑时,单子弹到处都是。 使用I / O操作monad,代码中的任何表达式都不代表用户的输入。 因此,系统状态不会在程序中的任何位置明确表示。 代码中的表达式与时间无关。 您永远不会问“在程序的这一点上x
是什么意思?” 而是,您问“ x
在该程序中一劳永逸是什么意思?”
记住底线...
我们生活的世界是有状态的,对此我们无能为力。 Monad不会从系统中消除状态。 但是monad消除了程序代码中对状态的提及。 那可能很有帮助。
翻译自: https://www.infoq.com/articles/Understanding-Monads-guide-for-perplexed/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
http指南单子版
http指南单子版_了解单子。 困惑的指南相关推荐
- python数据科学指南是什么_《Python数据科学指南》——导读
前 言 如今,我们生活在一个万物互联的世界,每天都在产生海量数据,不可能依靠人力去分析产生的所有数据并做出决策.人类的决策越来越多地被计算机辅助决策所取代,这也得益于数据科学的发展.数据科学已经深入到 ...
- python入门指南阅读答案_【python】入门指南1
基础的数据结构:int, float, string 注意:python入门系列的文章的示例均使用python3来完成. #!/bin/python a= 1b= 1.0c= 'string' pri ...
- 模仿Hibernate的逆向工程_java版_源码下载
在这篇blog:"Hibernate逆向工程原理_java版本"中谈到了Hibernate逆向工程原理. 我喜欢理论和实践相结合....so,今天我试着模仿hibernate的逆向 ...
- 启动rocketmq_RocketMQ 部署启动指南-Docker 版
最近学习使用 rocketmq,需要搭建 rocketmq 服务端,本文主要记录 rocketmq 搭建过程以及这个过程踩到的一些坑. 准备工作 在搭建之前,我们需要做一些准备工作,这里我们需要使用 ...
- 《UML用户指南第二版》再次温读笔记(一)(downmoon)
前言:最近,花点时间重读(也不知道是第几遍了)<UML用户指南第二版>这本书,感觉虽然对WEB程序开发而言,UML的应用是一个极大的挑战,然而,其中蕴含的基本原理和指导性却是历久弥新,耐人 ...
- 公司网站Silverlight版^_^
公司网站Silverlight版^_^ 网站地址:http://www.ichinagames.com/Silverlight/ 预览图: posted on 2010-01-16 13:47 now ...
- 23V3有这种C语言表达式吗,数据结构(C语言版第2版_李云清)习题答案2012-12.doc
数据结构(C语言版第2版_李云清)习题答案2012-12.doc 第 1 章 绪论 1.1 什么是数据结构? [答]:数据结构是指按一定的逻辑结构组成的一批数据,使用某种存储结构将这批数据存储 于计算 ...
- H5活动产品设计指南基础版
本文来自 网易云社区 . H5一般页面不会很多,看似简单,实际上会有很多细节需要注意,我自己在做过了几个H5之后,发现了一些常犯的问题,做了小结,希望给新开始做H5的产品相关的同学提供一些帮助. 首先 ...
- 计算机中丢失msc,mscvr120.dll32位/64位版_修复计算机中丢失msvcr120.dll
mscvr120.dll32位/64位版_修复计算机中丢失msvcr120.dll mscvr120.dll是系统的非常重要的一个文件,相信很多的人都是遇到文件丢失的情况,这个时候就需要你在下载一个d ...
最新文章
- 三类常见软件质量(Quality Attribute)属性的通俗解释
- 【初探HTML本相】道之真谛不过自然,html标签脱俗还真
- 请不要轻易使用 is_numberic 加入存在E字母
- C++ 写时拷贝 2
- 《Objective-C入门经典》——2.1节Objective-C世界中的面向对象程序设计
- django解决使用DateTimeField添加、修改记录时不动态更新时间的问题
- 输入挂(bzoj 2901: 矩阵求和)
- 【最短路径】 SPFA算法优化
- wordpress不登陆后台禁用插件
- JAVA 如何将String进行大小写转换
- 海康nvr sdk java调用,海康SDK开发NVR拍照功能
- linux压力测试工具post,linux 下如何做压力测试 ab 压力测试 - 沃森博客
- 建立 rsyslog 日志服务器
- 2019华为软件精英挑战赛总结篇
- 移动端meta设置大全(持续收集中。。。。)
- 兑换记录html页面,兑换码记录.html
- 【企业为什么要进行数字化转型】之数字时代新模式
- JAVA 如何使用延迟
- 惊!搜狐邮箱乱添附件!!!!
- 数电课程设计,数字电子钟具有时、分、秒以及校时功能
热门文章
- python单引号打不出来_python里单引号怎么打
- Java char字符单引号
- linux php 编译安装_linux下编译安装配置php5.6.30过程
- 断了线的风筝,只能让它飞,放过它
- mac pro 升级ssd_您可以在Mac中升级硬盘驱动器或SSD吗?
- BMPFont使用教程--免费的位图字体制作工具
- Mysql面试大全,看完可以吊打面试官
- AutoML:人工智能领域-自动化技术之机器学习自动化技术的简介(预处理→设计算法→训练模型→优化参数)、常用的工具或框架之详细攻略
- 2021年熔化焊接与热切割模拟试题及熔化焊接与热切割模拟考试
- 毕设 房价预测分析与可视