热度 7
2020-1-30 11:20
1796 次阅读|
0 个评论
通过例子和实践来学习rho语言。下面的例子和练习都很值得去运行、阅读、修改和完善。修改练习和教程中任何你感到有意思的代码,这样能够获得最好的学习效果。该教程包含了rho语言最常见以及最重要的特性,足以让开发者快速入门。 课程0 -- 开发环境 配置你的开发环境 为了可以运行这个教程里面的rholang代码,你需要一些开发环境。 这不是一个会让你感到疲惫的rholang开发工具或者技术栈。 然而它展示了一些基本的开发环境给你开始。 网上编译器 RChain社区的成员提供了一个基于公共网站的在线rholang编译器。 这个工具非常有前途,也是一种入门的简单方式。 但是它还是开发节点,有时候会不稳定。 本地节点 真正正确运行rholang代码的方法是在通过启动你自己本地机子的RNode然后使用它的rholang编译器。 首先你要为你自己的平台安装 RNode 对于初学者,这里有详细的一步一步指导你怎么使用AWS 或者Docker启动你的节点. 一旦你的RNode安装好了,你可以运行基本的独立节点。 $ rnode run -s -n 在单独的终端里,你可以在REPL模式下一次执行一行rholang。 $ rnode repl ╦═╗┌─┐┬ ┬┌─┐┬┌┐┌ ╔╗╔┌─┐┌┬┐┌─┐ ╦═╗╔═╗╔═╗╦ ╠╦╝│ ├─┤├─┤││││ ║║║│ │ ││├┤ ╠╦╝║╣ ╠═╝║ ╩╚═└─┘┴ ┴┴ ┴┴┘└┘ ╝╚╝└─┘─┴┘└─┘ ╩╚═╚═╝╩ ╩═╝ rholang $ Nil Deployment cost: CostAccount(0,Cost(0)) Storage Contents: for( x0, x1 <= @{Unforgeable(0x01)} ) { Nil } | for( x0, x1, x2, x3 <= @{"secp256k1Verify"} ) { Nil } | for( x0, x1 <= @{"sha256Hash"} ) { Nil } | for( x0, x1 <= @{Unforgeable(0x03)} ) { Nil } | for( x0, x1, x2, x3 <= @{"ed25519Verify"} ) { Nil } | for( x0, x1 <= @{"blake2b256Hash"} ) { Nil } | for( x0 <= @{Unforgeable(0x02)} ) { Nil } | for( x0 <= @{Unforgeable(0x00)} ) { Nil } | for( x0, x1 <= @{"keccak256Hash"} ) { Nil } rholang $ @"world"!("hello") Deployment cost: CostAccount(5,Cost(64)) Storage Contents: @{"world"}!("hello") | for( x0, x1 <= @{Unforgeable(0x01)} ) { Nil } | for( x0, x1, x2, x3 <= @{"secp256k1Verify"} ) { Nil } | for( x0, x1 <= @{"sha256Hash"} ) { Nil } | for( x0, x1 <= @{Unforgeable(0x03)} ) { Nil } | for( x0, x1, x2, x3 <= @{"ed25519Verify"} ) { Nil } | for( x0, x1 <= @{"blake2b256Hash"} ) { Nil } | for( x0 <= @{Unforgeable(0x02)} ) { Nil } | for( x0 <= @{Unforgeable(0x00)} ) { Nil } | for( x0, x1 <= @{"keccak256Hash"} ) { Nil } 当你运行更多行数的rholang代码时候,你可以使用RNode的eval模式来执行代码。 $ rnode eval intersection.rho Evaluating from intersection.rho Result for intersection.rho: Deployment cost: CostAccount(39,Cost(1132)) Storage Contents: @{Unforgeable(0xb19519ab773d1ec4ce96f1b71b748552e4a084dfc9942371717f5cb87e818879)}!(@{"name"}!(Nil)) | @{Unforgeable(0xb19519ab773d1ec4ce96f1b71b748552e4a084dfc9942371717f5cb87e818879)}!(@{"age"}!(Nil)) | @{"world"}!("hello") | for( x0, x1 <= @{Unforgeable(0x01)} ) { Nil } | for( x0, x1, x2, x3 <= @{"secp256k1Verify"} ) { Nil } | for( x0, x1 <= @{"sha256Hash"} ) { Nil } | for( @{{@{"name"}!( ) | _ /\ @{"age"}!( ) | _}} <= @{Unforgeable(0xb19519ab773d1ec4ce96f1b71b748552e4a084dfc9942371717f5cb87e818879)} ) { @{Unforgeable(0x00)}!("Both name and age were in the data") } | for( x0, x1 <= @{Unforgeable(0x03)} ) { Nil } | for( x0, x1, x2, x3 <= @{"ed25519Verify"} ) { Nil } | for( x0, x1 <= @{"blake2b256Hash"} ) { Nil } | for( x0 <= @{Unforgeable(0x02)} ) { Nil } | for( x0 <= @{Unforgeable(0x00)} ) { Nil } | for( x0, x1 <= @{"keccak256Hash"} ) { Nil } 有一些RNode的输出会出现在你运行代码的同一个终端。但是其它一些代码输出会直接出现在第一个终端。 所以在你熟悉什么输出出现在哪里前请确定好检查两边的终端。 Cryptofex IDE 一个叫做cryptofex 的开发环境已经进入了alpha版本。 Cryptofex可能最后最好的开发rholang的地方,但是现在还是很早期的软件。 Cryptofex提供rholang语法高亮特性并且可以在RChain集成节点上检测dApps。 IDE同时也提供环境创建和测试在以太网上,私人测试网上和单独模式的EVM上的智能合约。 课程1 -- 发送与标准输出(stdout) 发送与标准输出(stdout) 说声Hello "Person waiving hello" 编程界有一个存在已久的传统——输出"Hello World"应该是你学习的第一个程序。下面是一个在屏幕上输出"Hello World"的最简单例子。 hello.rho 练习 请让程序输出"Rholang rocks!" 而不是 "Hello World"。 练习 尝试将"stdout"替换为别的语句。会得到什么结果? 尝试一下这个有趣的通道名称@"someChannel". 这里可以比较随意。请让程序在屏幕上输出 "Sup World"。 标准输出(stdout)到底是什么东西 Channels are like mailboxes for sending messages rho语言的核心是通道(channel,下面都称为通道)通信. 通道是你可以用来发送和接收消息的通信线路。你可以使用!字符来在通道中发送消息。 Redo this diagram! stdout 是一个特殊的通道,用于将文本发送至"标准输出",通常指你的电脑屏幕。正因为它的特殊,我们不得不将它写在第一段学习的代码里面。 使用其他通道 Sent messages wait to be received here in "message purgatory"... JK, it's called the "tuplespace" 实际上你可以在很多通道中发送消息,而非只有stdout。 但其它通道不像 stdout 他们不会在屏幕上显示。 tupleSpace.rho 那么,在其他通道中的消息将被发送至哪里?哪里都不会去!这些消息暂时哪儿都不去,这些消息会继续待在通道内,等待其他人去取出它们。我们将在下一课程中学习如何获取这些消息。同时,消息滞留所在的地方,我们称为 "元组空间"。 请确保你的信息保留在元组空间里。你应该会看到像下面的信息。 Storage Contents: @{"RandoChannel"}!("This won't be on the screen") | for( x0, x1 <= @{Unforgeable(0x01)} ) { Nil } | for( x0, x1, x2, x3 <= @{"secp256k1Verify"} ) { Nil } | for( x0, x1 <= @{"sha256Hash"} ) { Nil } | for( x0, x1 <= @{Unforgeable(0x03)} ) { Nil } | for( x0, x1, x2, x3 <= @{"ed25519Verify"} ) { Nil } | for( x0, x1 <= @{"blake2b256Hash"} ) { Nil } | for( x0 <= @{Unforgeable(0x02)} ) { Nil } | for( x0 <= @{Unforgeable(0x00)} ) { Nil } | for( x0, x1 <= @{"keccak256Hash"} ) { Nil } 同时做两件事 Rather than following an ordered list, all ingredients are added concurrently. Looks delicions 在rholang中,我们不会告诉计算机做完一件事,再到另一件。相反,我们会告诉它需要做的所有事情,然后"并行地"执行它们,或者一次性全部执行。 parallel.rho | 的发音是 "parallel", 可简称为 "par"。 练习 向"pizza shop"通道发送消息"1 large pepperoni please"。 练习 向"Mom's Phone"通道发送"Hi Mom"。 练习 用一个程序在屏幕上输出两个消息,"Rick"和 "Morty"。 小测试 stdout!("Programming!") 将在屏幕上输出什么? Programming! stdout! Nothing @"what"!("Up") 在什么通道上发送消息? @"Up" @"what" what rholang会先执行哪一条语句? @"stdout"!("Dogs") | @"stdout"!("Cats") 输出 "Dogs" 输出 "Cats" 都不。 它们是并行的 PS. 有一个特殊的通道 stderr. 请尝试一下看看往这个通道发送消息,会发生什么? 有什么区别? 课程2 -- 接收 消息检查 // Dear future self, keys in freezer because... 在上一章我们学习了如何发送消息。现在是时候学习如何接收消息了。常规语法如下: for(message <- channel){ // Do something here} 顺便提一下, // 用于标示注释。 //后面的内容程序并不会运行。写好注释可以有利于其他开发者(包括你自己)阅读代码,并了解代码的意图,其他读你代码的开发者会感激你写注释的。 通信事件 Pizza shop can receive messages on its channel. 下面的代码使用披萨店的通道发送了一个消息,披萨店收到了它。pizza店通过将消息打印至标准输出来表明其已收到。 pizzaOrder 练习 将上述消息发送至一个不同的通道,如@"coffeShop". 消息会被接收端打印出来吗? 还是东西留在了元组空间里么? Let's hit up the coffee shop. 练习 记住,在rholang中,任何事情都是并行地而非按顺序地执行。如果我们把接收信息的代码放在前面,那么披萨店的代码仍可执行。尝试一下吧。 元组空间污染 如果你遇到了旧数据滞留在元组空间并会对后面的代码执行有影响,你需要清空你的元组空间。最简单的方式是删除你的数据目录.rnode 使用上述方法清空元组空间已经过时了。一个更好的方法是防止它一开始被旧数据污染。我们可以通过修改最上面的new代码段来实现。 旧的方案 new stdout(rho:io:stdout) in { @"world"!("Welcome to RChain") } 尝试下面新的方案 new world, stdout(rho:io:stdout) in { world!("Welcome to RChain") // No more @ or " " } 我们将在“不可伪造的names”的课程中讲解它的原理。现在你不需要每次都重置通道。 发送前接收 Rather than the message appearing first, then someone receiving it, Greg is trying to receive first. Hopefully someone will send him a message so he can have a comm event. 当发送和接收同时存在于通道时,这被称为通信事件,或称为"comm event"。 不像普通邮件那样必须被发送,对方才能被接收,在rholang中,上述两个事件可以以任何顺序发生或者同时发生。这类似于可以先接收消息,再发送它。每当发送和接收共存时,就会触发通信事件。 合约 The poor chef is too busy making sure he can receive orders to take care of his pizza. 我们的披萨店例子很好地说明了通信事件,但期望每次有新的订单时,披萨店都能自动发出一个新的接收来处理它们,这并不现实。 幸运地是,我们可以只部署一次代码,然后每次接收到它的消息时都执行一次。这类代码称为“智能合约”。让我们看一个比披萨店更高级但相似的例子--咖啡店。 coffeeShop.rho 练习 在咖啡店点第二杯饮料 练习 更改上面例子的确认消息 一般来说,下列哪一个会第一个发生? 发送,因为它与普通邮件的工作原理一样。 接收,因为以该方式运行的代码更快。 发送或接收都可以最先发生,或者同时。 接收,因为rohlang是并行的。 都不。直接触发通信事件(comm event)。 练习 通道被命名为 @"coffeeShop"。将它更名为你所选择的特定咖啡店的名称。然后使用我们最近学到的new来修改代码 Persistent For 实际上,在rholang中有两种不同的语法来表示持续从通道取出信息。我们刚刚学习contract语法。下面的用for语法的代码是等价的。 contract @"coffeeShop"(order) = { for(order <= @"coffeeShop") { 注意,上述代码与正常的 for 不同,因为它使用了双划线 <= 而不是单划线 <-. for和contract是有不同的地方的,我们会在讨论区块链的时候讨论到他们的区别。现在你可以将它们当做同一功能。 练习 用持久的for语法而不是"contract"语法来写一个想咖啡店这样的披萨店合约。尝试自己从头写一次整个代码,这样会让你更容易记清语法。 下面哪一项是与其他两项不同的? for (a <- b){} contract b(a) = {} for (a <= b){} 哪一个发送语句会与for (message <- @"grandmasSnapChat"){Nil}对应产生一个通信事件 ? grandmasSnapChat!("Hi Grandma") @"grandmasSnapChat"!("Glad you're snapping Grandma") for("Here's a snap for you g'ma" <- @"grandmasSnapChat") 课程3 -- 传音筒、"name"和“process” 消息传递 The game of telephone is perfect to simulate message forwarding in rholang. 在前面的章节,我们学习了如何向祖母或披萨店发送消息。但是至今所有的接收方都通过将消息打印至标准输出,来告知已经接收到了。 现在让我们做一些更有意思的事情--类似孩子们的传话游戏那样传递消息。 telephone3.rho 你可以通过运行上面的代码来做实验。你可以修改你觉得合适的地方多运行几次。 练习 传话游戏很有趣,但有更多玩家参与会更好。请添加第三位明教Charlie的玩家。bob接收消息后将发送消息给Charlie,而不是简单打印至stdout。然后Charlie将它打印至屏幕上。多多益善! The message never seems to get there correctly. I blame Bob. 练习 如果你曾经玩过电话游戏,你应该知道,消息极少能被正确地传递。Bob现在决定通过发送一条错误的消息。改写程序,使得Bob无论收到什么,都能传递不同的消息。 *这到底是啥? Opposites attract 你注意到 @"Bob"!( message)中的 ? 在rholang中有两种类型, "names" 和 "processes"。同样也有可以在两者之间互相转化的方法。 "processes"可以是rholang中任何一个代码片段,例如我们的传话筒游戏,或者是披萨店订单程序。“process”可以是上百行的大程序,也可以只有几行。它们甚至可以是用于表示值的代码。下面是一些“process”的例子。 stdout!("Sup Rholang?") 一个常见的发送操作。 Nil 最小的“process”。如字面意思,它不做任何事。 for(msg <- @"phone"){Nil} 一个常见的接收操作,在消息到达时它不会做任何事。 "Hello World" 另一个不做任何事请的小“process”。被称为"基础术语"。 "names"可以被用于赋名通道以发送消息。在大多数编程语言中,"name"是完全独立的一样东西,它们本身就存在。但是在rholang中,"name"来自"引用process",即将@标签放在“process”之前,即可得到一个"name"。下面是"name"的一些例子。 @"Hello World" 通过引用基础术语"Hello World"来创建。 @Nil 最小的“name”。通过引用最小的“process”来创建。 @(@"Alice"!("I like rholang, pass it on.")) 通过引用来自传话筒游戏的"process"来创建。 关于*的一切 What kind of name is that!? Did your parents just name you after some computer code? 通过用@符号来标记“process”,我们可以将“process”打包以创建一些“name”。我们也可以通过使用*标记“name”,从而将“name”转变为“process”。 在rholang中,我们需要记住的是发送“process”和接收“name”。这很重要,因此我再次强调。你总是发送一个“process”,在另一端接收一个“name”。 Aice通过for(message <- @"Alice")接收我们的消息,所以, message 变成了一个“name”。当她之后发送给Bob时,她不得不发送“process”,所以她要用@"Bob"!( message)使用 将message转变回一个“process”。 小测验 我们发送什么? processes names 我们接收什么? processes names @"registration"是什么? process name 非法语法 Nil是什么? process name 非法语法 @Nil是什么? process name 非法语法 @@Nil是什么? process name 非法语法 *importantData 是一个“process”, 那么importantData是什么? process name 非法语法 下面哪一个与"BobsPhone"等价? *@"BobsPhone" @"BobsPhone" *"BobsPhone" @*BobsPhone stdout!("BobsPhone") 练习 This telephone game has a fork 不像之前的线性传话游戏那样,每个玩家将信息传递给下一位,我么来为游戏添加一个分支。现在,Bob与先前一样将发送消息给Charlie,但同时也会发送给Elise。 每个分支的长度由你定,但在每个分支的最后都得将消息打印至标准输出。 课程4 -- 持续发送与窥探 为什么要重复发送? This radio navigation aid helps airplanes navigate by broadcasting the same message over and over 我们的披萨和咖啡店都可以在同一个复用通道中接收消息。我们使用一个持续的for (msg <= chan){...}或者一个合约contract chan(msg){...}来达成这一目的。 空中交通管制塔楼可能会乐于做刚好相反的事——不停地发送相同的消息。塔楼中的控制者希望记录同时包含天气和跑道信息的消息,并且提供给所有需要的飞行员。类似披萨店, 他们很繁忙,不会费力地在每次飞行员需要时都不停地发送信息。 持续发送的语法 控制塔需要在代码上做较小的调整,以使得发送操作能够持续。他们会使用!!而非单个!。 persistentSend.rho 请自行确认一下,原先发送的消息是否仍然在元组空间内。 练习 注意上述代码,第二名飞行员同样能够接收到信息。发送仍在持续。 对了,你注意到了吗?当我们实际上并不使用stdout时,我们不需要new stdout(...) in {} for (x <- y) {Nil} | y!!(Nil)中有多少次通信事件发生? 1 很多次 0 二次检查消息 正如我们刚才展示的,持续性发送和接收非常有用。但是,普通的发送和接收也同样足够好了。设想这样的场景:我将一个字母发送给祖母,她接收到了这个消息。 grandma.rho 现在我们设想:我想要二次检查我是否给她发送了正确的时间。我可以简单地取出这条消息,但这样一来她就没法读取这个消息了。 练习 依据你所知道的,你可以通过获取这个消息,自行检查它,再将它发送回旧的通道,以达到我们的目的。 请自行尝试上面的方案。答案已列在下面。 for (x <= y) {Nil} | y!!(Nil)会产生多少个通信事件? 1 很多个 0 答案 grandmaCheck.rho 窥探语法 Maybe I'll just peak at Grandma's letter through the envelope. rholang以后会为观察通道内变量提供一个特殊的语法。目前我们还不能使用它,但是下面会展示给你看这个语法的用法。我们将使用