Actinium ACM
$0.0149 2.01%
ACM是支持闪电网络和TOR的加密货币
关于Bosque编程的文章:博斯克的第一步
梦痕无忧

几天前,我偶然发现了一种有趣的新编程语言,称为Bosque。作为Microsoft Research的一个项目,Bosque仍然是一个不断发展的目标,并且可能会发生许多变化,但这并不妨碍我们探索它的一些最有趣的方面。

根据自己的定义,Bosque引入了一种新的编程范式,称为正则化编程。如果回顾过去的goto编程和意大利面条代码的糟糕日子,我们会立即意识到存在各种循环的存在,这些循环使我们今天的编程更加容易编写和推理。您能想象编写没有任何循环的复杂应用程序吗?但是,一切都是有代价的。您需要不断考虑循环在您的程序中引入的所有不变量来进行支付。同样,使用循环并不意味着只需要处理语义。每个循环(无论是for-eachwhiledo-while)都使用变量进行计数,通常与迭代器,在编码或调试时必须牢记。这也增加了复杂性,因为您必须在每个时间点都确保每个临时用户都是有效的,并且不会干扰代码的其他部分。简而言之,尽管循环比goto更好,但循环却要付出一定的代价。尽管结构化编程使我们摆脱了处理gotos和无 类型变量的麻烦,但由于它比整数字节数组更容易推理整数字符串浮点数,因此它也引入了自70年代以来必须解决的自身问题。我们称这些问题为偶然的复杂性。而Bosque则将其全部删除。是的,Bosque没有循环!

Bosque的另一个关键方面是其变量默认情况下不可变的。受功能语言的强烈影响,它从功能语言中汲取了很多东西。特别是不可变性对于避免共享可变状态引入的问题非常有用。我认为我不需要多说几句就可以使您相信管理可以被代码的不同部分修改的变量是一种令人沮丧的经历。 

我可以继续写下Bosque的许多其他有用方面,例如消除引用相等性和使用更安全和抽象的复合相等性概念进行混叠,但是我认为我们不需要1000个单词就能理解事物的美。不要尝试安装Bosque并编写一些代码来显示其某些优点。

安装博斯克

像任何优秀的编码器一样,我们直接进入GitHub并克隆Bosque的代码。作为一个开源项目,任何人都可以参与其中,但是请注意:这个项目是新的,因此那里的代码和文档是不断变化的。您可以肯定,本教程的某些部分很快就会过时或至少不完整。  

git clone https://github.com/microsoft/BosqueLanguage

当我在Mac上工作时,以下示例将与大多数* nix系统兼容。如果您使用的是Windows,请相应地适应DOS / PowerShel,或使用Windows Subsystem for Linux。 

克隆代码后,我们需要下载NodeJS并在全球范围内安装TypeScript,因为Bosque的工具链依赖于它们来生成C ++代码。现在,如果您问自己:但是为什么呢?,答案是Bosque的工具链是用TypeScript编写的,它在NodeJS之上运行,带您的代码生成C ++代码,然后由支持的C ++编译器之一对其进行编译。

Your Bosque Code -> NodeJS/TS toolchain -> transpiles into C++ code -> C++ compiler compiles -> Your App

基本上,只需单击几个按钮即可安装NodeJS。只需将其安装在默认目录中即可。要全局安装TypeScript,您必须发出此npm命令。

npm install -g typescript

由于npm与NodeJS一起提供,因此在安装后应立即可用。如果您是通过控制台进行安装的,请重新启动它以更新本地PATH

现在转到克隆的Bosque目录,然后输入子目录impl。这是Bosque工具链的所在地。我们将必须首先编译它。为了使此方法成功,我们必须首先安装所需的npm软件包。

npm install

我们的下一条命令执行构建脚本(有关更多信息,请检查impl目录中的package.json)。 

npm run build

您将看到类似于以下的输出:

bosque-reference-implementation@0.5.0-rc-1 build /Users/brakmic/projects/BosqueLanguage/impl

> tsc -p tsconfig.json && node ./build/resource_copy.js

Copying resources...

Copying /Users/brakmic/projects/BosqueLanguage/impl/src/core to /Users/brakmic/projects/BosqueLanguage/impl/bin/core

Copying /Users/brakmic/projects/BosqueLanguage/impl/src/tooling/aot/runtime to /Users/brakmic/projects/BosqueLanguage/impl/bin/tooling/aot/runtime

Copying /Users/brakmic/projects/BosqueLanguage/impl/src/tooling/aot/runtime/bsqcustom to /Users/brakmic/projects/BosqueLanguage/impl/bin/tooling/aot/runtime/bsqcustom

Copying /Users/brakmic/projects/BosqueLanguage/impl/src/tooling/bmc/runtime to /Users/brakmic/projects/BosqueLanguage/impl/bin/tooling/bmc/runtime

Copying /Users/brakmic/projects/BosqueLanguage/impl/src/test/tests to /Users/brakmic/projects/BosqueLanguage/impl/bin/test/tests

done!

您已成功安装Bosque。大!

通常,下一步是从impl / bin / runtimes / exegen.js可执行文件使exegen.js脚本成为可执行文件,这样我就不必每次都要编译某些东西时都为节点加上前缀。但是,由于我只有一个对* nix系统有用的脚本,因此在这里避免推荐它。如果您想尝试一下,请转到我的Bosque存储库的此分支并将其克隆。 

您需要知道的是,用于解析,转换和编译程序的工具位于impl / bin / runtimes / exegen下。它是用与node一起运行的JavaScript编写的

node impl/bin/runtimes/exegen/exegen.js -c clang++ -o your_app your_app_source.bsq

Bosque工具链支持各种C ++编译器,因此您可以尝试不同的编译器。我使用clang ++g ++ 9。生成的二进制大小之间的差异不是很大,您还可以使用该--level标志在调试发布之间切换。

你好世界在博斯克

像大多数其他编程语言一样,Bosque也需要一个我们用关键字entrypoint声明的起点。 

namespaceNSMain;


entrypointfunctionmain(arg:String):String{

returnString::concat("Hello", ", ", arg, ".");

}

我们使用单个参数定义一个函数,该函数对字符串执行某些操作。如果您是经验丰富的面向对象语言开发人员,或者至少具有脚本编写经验,那么在确定关键要素方面应该没有问题。首先,这种语言是基于卷曲的,就像C,C ++,C#,Java等。然后是名称空间的概念,这可能导致假设某些元素(可能是数据类型)通过命名某些名称空间而被包含在内。在这种情况下,我们要定义一个必须在NSMain名称空间中定义的入口点函数

总而言之,这似乎与其他编程语言没有太大不同。除了entrypoint关键字之外,我们还没有看到任何令人兴奋的东西。而CONCAT方法调用看起来非常像那些典型的C ++静态方法。双冒号对C ++程序员而言并不是什么新鲜事物。实际上,在Bosque中,必须对双冒号使用静态方法。在我们的例子中,我们想连接一些字符串,然后再将其返回。

函数的返回值类型在函数名称之后定义,并以单个冒号开头。在我们的例子中是String。Bosque定义了几种类型,几乎可以在每种“现代”编程语言中找到。我们有IntStringBoolListMap等等。但这很重要:尽管我们可以创建和使用它们,但是无法在函数内的控制台(或任何其他类型的输出)上将它们打印出来。是的,Bosque函数可能看起来像普通的C或C#函数,但它们的行为更像Haskell的函数。在Bosque中,功能没有副作用。而且写到控制台令人发指副作用。因此,这是不可能的。

没有副作用和不变性

Bosque将OO /结构化编程语言中的生产要素与更强大的功能方面结合在一起。这就是为什么我们可以具有副作用的免费花括号功能。不仅如此。默认情况下,我们还有不可变的变量,可以在花括号中更改它们。让我们看一下这段代码:

namespaceNSMain;


functionincrease(arg:Int):Int{

  arg = arg + 1;

returnarg;

}


entrypointfunctionmain(num:Int):Int{

returnincrease(num);

}

会编译吗?好像是我们只是在接受一个参数,并希望通过调用递增函数来对其进行修改。看起来很无害,不是吗?在Bosque中并非如此,我们可以从此编译器错误中读取:


exegen.js --c clang++ -o immutable immutable.bsq

Compiling Bosque sources in files:

immutable.bsq

...

Parse error -- ["immutable.bsq",4,"Variable defined as const"]

变量arg只是我们主要函数中的num,不能在函数内部进行更改。默认情况下已声明为const。相反,我们应该做的是修改值,而不必尝试将其保存回原始变量中。这是将编译的修改后的代码:

functionincrease(arg:Int):Int{

returnarg + 1;

}

当我们编译并执行程序时,它将返回期望值:

./immutable 10 

11

默认情况下,不变性是一项强大的功能,因为它使程序员不必考虑共享的可变状态。但是,与Haskell等更著名的函数式编程语言不同,Bosque允许花括号内部具有可变性。你可以自由地定义为许多变种,只要你喜欢iables和变异他们。可以确定的是,它们永远不会离开这些花括号定义的范围。这里是一个例子:

namespaceNSMain;


functionchange_inside(arg:Int):Int{

vartemporary = 10;

  temporary = temporary + arg;

returntemporary;

}


entrypointfunctionmain(num:Int):Int{

returnchange_inside(num);

}

这段代码编译并执行没有任何问题:


./mutable 10 

20



灵活的函数调用

到目前为止,我们仅将单个参数传递给函数。但是Bosque可以做的更多,更多。显然,我们可以传递多个参数,这并不是什么例外。真正好的一点是能够使用休息散布概念以及命名参数传递。您可能已经知道其他编程语言中的前两个。在JavaScript中,其余概念将如下所示(我们在浏览器控制台中执行):

我们有一个带有四个参数的函数,但是我们只分别输入其中的前两个参数。其余参数来自一个分布在这两个数组上的数组。该三个点阵列前面表明的JavaScript会蔓延这些元件在剩余的参数。 

其余的概念在形式上类似,但其用于解决不同类型的问题:当有参数未知数量的功能将得到

让我们执行这个例子:

由于该函数可以接收任意数量的参数,因此我们必须确保每个参数都会得到相应处理。在这里,我们使用相同的运算符(三个点)来表示,每个给定参数将被视为“下一行”,并由位于process_rest_params函数中的reduce函数进行处理。

Bosque也支持这两个概念:

namespaceNSMain;


functiongetmin(...args:List<Int>):Int{

  returnargs.min();

}


entrypointfunctionmain():Int{

  returngetmin(10,6,22,8,50,24);

}


./spread

6


上面的代码返回通过获取所有参数(无论存在多少参数)而构造的数组的最小元素。函数getmin可以采用任意数量的单个参数,因为所有这些参数都会首先转换为List<Int>。

与JavaScript示例类似,我们也可以实现传播概念:


namespaceNSMain;


functioncalculate(a:Int, b:Int, c:Int, d:Int):Int{

returna + b + c + d;

}


entrypointfunction main():Int{

letarray = [22,33];

returncalculate(10, 6, ...array);

}

./spread 

71


在上面的示例中,我们需要四个变量来完成我们的计算。但是,它们的后两个不是作为单独的参数出现,而是作为一个数组“扩展”到变量cd上。正如我们所看到的,传播休息这两个概念非常有用,但这还不是全部。Bosque可以做得更多。让我们通过使用命名参数传播参数扩展上述示例。

entrypointfunction main():Int{

letarray = [22,33];

returncalculate(b=10, a=6, ...array);

}

好的,这是一个人为的例子。您可能还注意到我们一直在使用另一个关键字:let。这是针对在块范围内不会更改的变量。除了用var声明的“变量”变量外,我们还有使用let关键字声明的常量变量。

无循环

正如开始时已经宣布的那样,Bosque中没有循环。没有for-eachwhile-dodo-while和整个其余部分。循环越多,就越难推理程序可能采用的执行路径。尽管有助于避免更糟的goto-constructs循环有其自身的缺点。您是否曾经在索引变量或空指针迭代器方面遇到问题?但是,我们仍然需要以某种方式循环,以重复执行程序中的某些部分,以遍历列表,集合和地图。很少有程序直接从A到B再到C再完成。那么,当根本没有循环语句时,我们如何在Bosque中创建循环结构?您可能已经想到了:答案可以在函数式编程中找到。几乎任何循环都可以转换为更高级别的函子之一,例如filter,findmapreduce。另外,我们可以应用列表推导在元素组上,因此几乎没有任何理由手动构建循环。软件开发人员应该考虑解决实际问题,而不是考虑循环,共享的可变状态和各种“礼仪”元素。让我们尝试这个示例,看看Bosque如何实现循环:

namespaceNSMain;


entrypointfunctionmain():List<Int> {

varlist =List<Int>@{1,2,3,4};

returnlist.map<Int>(fn(e) => e * 2);

}


成功编译后,我们执行代码并获得以下结果:


./loops

{2, 4, 6, 8}


在这个小例子中,我们看到了一些有趣的事情。首先,使用List<Int>的默认构造函数实例化@符号和{}大括号。稍后,我们将了解所有这些工作原理,但现在我们将继续关注循环。我们的阵列发生了什么,它在哪里发生?答案是在以函数作为参数的map成员函数调用中。这由关键字fn及其后面的自己的参数列表指示。Bosque像其他任何类型一样对待函数。在我们的例子中,该函数需要一个参数来表示一个元素,该元素将来自调用该数组的数组地图方法。在函数内部,当前元素将乘以2。最终结果是一个包含乘积的新数组。因此,我们不是创建一个遍历数组并通过索引变量来跟踪其当前位置并通过它访问元素的for循环,而是通过为map方法提供适当的乘法函数来优雅地解决了该问题。那些已经使用Haskell或任何其他功能语言(包括JavaScript进行编程)编程的人,本能地知道这种方法的优点。与其告诉计算机做什么,我们不如告诉计算机结果应该是什么样。其余的来自机器。没有共享状态,没有索引变量,没有直接元素访问。

概念和实体

如前所述,Bosque支持我们从面向对象的语言中了解的构造。我们有构造函数和用户定义的数据类型,在Bosque中称为实体。还有一些类似接口的构造,称为概念,它们被用作实体的蓝图。但是,我们将讨论一些细微的差异。这是提供概念的实体的简单示例(在OO语言中,我们会说“它实现了接口”)。

namespaceNSMain;


conceptAnimal {

  fieldname:String;

  abstractmethodsay():String;

}


entityDogprovidesAnimal {

methodsay():String{

    returnString::concat(this.name, " says ", "woof!");

  }

}


entrypointfunctionmain(dog_name:String):String{

  letdog =Dog@{name=dog_name};

  returndog.say();

}


我们让我们的狗Max跟我们说话!🙂


./entities Max

"Max says woof!"


我们的程序从动物概念定义开始,该概念描述了提供它的实体将具有的属性(字段)和方法。然后是一个实体定义,在这种情况下是Dog,它定义了一个表示抽象方法实现的方法say(): String。大致上,这类似于来自其他各种OO语言的抽象方法和实现。我们还注意到,我们的Dog实体没有引用该name字段,因为作为Animal概念的提供者,它已经可以访问它。然后,我们通过调用其构造函数实例化该实体。如前所述,我们使用@符号和大括号{}调用构造函数。但是,Bosque构造函数的行为不同于您在C ++,C#等中发现的构造函数。Bosque而不是使用许多super()base()调用来创建复杂的层次结构,而是采用直接字段初始化的方式。我们还说Bosque构造函数是原子的。而类似于其它面向对象的语言,博斯克也有此表示实体实例“指针”变量。我们还可以介绍仅属于实体构造的静态方法。也就是说,只能使用实体名称而不是其实例来调用它们。

entityDogprovidesAnimal{

methodsay():String{

   returnString::concat(this.name, " says ", "woof!");

  }

  staticplay():String{

   return"All dogs like to play!";

  }

}


静态方法play(): String只能这样调用:Dog::play()。静态方法的调用类似于C ++。此外,我们可以覆盖实体内部的方法实现。在这里,我们更新了Animal概念以包含方法的定义eat(): String。顺便说一句。这也是一个很好的例子,说明概念比其他OO语言中的接口更强大,因为它们可以包含方法定义,而不仅仅是声明。

conceptAnimal{

  fieldname:String;

  abstractmethodsay():String;

  methodeat():String{

   return"tasty!";

  }

}


我们的Dog实体还包含一种方法eat(): String可以从概念中覆盖该方法。


entityDogprovidesAnimal{

  methodsay():String{

    returnString::concat(this.name, " says ", "woof!");

  }

  staticplay():String{

    return"All dogs like to play!";

  }

  overridemethodeat():String{

    return"very tasty!";

  } 

}


如果执行此变体,结果将是这样:


./entities Max 

"very tasty!" 


我们还可以选择引入类似于其他OO语言中的私有方法的隐藏方法。在这里,我们修改了我们的Dog来捕猎猫[免责声明:在撰写本文时,没有动物受到伤害。]🙂


entityDogprovidesAnimal{


overridemethodeat():String{

returnthis.hunting_cats();

  }

hiddenmethodhunting_cats():String{

return"it's so much fun!";

  }

}


修饰语staticoverridehidden也可以组合使用。因此,可以覆盖静态方法或将其隐藏。但是,不仅程序员要处理概念。Bosque的核心库本身定义了许多库。例如,有一个称为Parsable的概念,它描述了实体必须实现以被识别为可解析的内容

//<summary>Special type for indicating this type supports typed string use</summary>

conceptParsableprovidesAny{

abstractstatictryParse(str:String):Result<Any,String>;

}


您可以在此处core.bsq文件中找到此概念以及许多其他概念。


类型字符串

来自Bosque的另一个强大概念是键入字符串。这样的字符串包含一个“规则集”,用于确定一个字符串是否可以处理。我们也说这样的字符串包含一个Validator。让我们来看看它的作用:

namespaceNSMain;


typedefTimeFormat= /\b(0?\d|1[0-2]):[0-5]\d (AM|PM)/;


functionget_time_string(str:String):SafeString<TimeFormat> {

  returnSafeString<TimeFormat>::from(str);

}


entrypointfunctionmain(data:String):SafeString<TimeFormat> {

  returnget_time_string(data);

}

在这里,我们定义了一个用于解析带有AM|PM后缀的时间的正则表达式。您可能还会发现与C / C ++的typedef说明符有些相似。我们的函数get_time_string接受一个字符串,并将其转换为依赖typedef验证器进行构造的SafeString。如果一切正常,我们将返回正确的SafeString<TimeFormat>。否则,程序将崩溃。 

./typed_strings "00:15 AM" │

NSCore::SafeString<T=NSMain::TimeFormat>'00:15 AM'

这是给出无效数据时的样子:

./typed_strings "12345"

"Fail pre-condition" in /Users/brakmic/projects/BosqueLanguage/impl/bin/core/cpp/core.bsq on line 232

一个非常可怕的反应,不是吗?


但是我们不必忍受这一点,SafeString提供了更方便的方法,例如tryFrom。现在,我们将更新函数以处理不可解析的值:。


functionget_time_string(str:String):SafeString<TimeFormat>|None{

letresult =SafeString<TimeFormat>::tryFrom(str);

if(result == none) {

returnnone;

  }else{

returnresult;

  }

}


在这里,我们介绍一些新元素:


  1. if-then-else语句( 也可以使用带有elif关键字的else-if)
  2. 工会可能的返回值与标明|
  3. 没有概念也没有价值 

在上面的代码中,我们现在尝试解析有效时间,如果成功,我们将返回SafeString<TimeFormat>。如果失败,则返回的值将为none。其余代码与其他编程语言非常相似。我们使用if-then-else进行检查,并相应地返回值。如果发生故障,我们会得到比崩溃更糟糕的东西:

./typed_strings "12345"

none

结论

我希望我可以向您展示Bosque的许多有趣功能。还有很多,但是由于本文档应该有一个结尾,我想我将其留给下一个。我正在计划有关Bosque编程的文章系列。

但是,由于我本人还是新手,而且语言还在不断发展,因此此处编写的所有内容都可能会更改和/或根据我的假设,(错误的?)结论和错误而定。因此,我建议您阅读可用的文档遍历代码(TypeScript和C ++)。最重要的是:实验,实验,实验。 

与Bosque玩得开心!

2020年05月20日 20:47图文分享