2018 年 11 月

第 33 卷,第 11 期

.NET-使用符号委托创建您自己的脚本语言

通过Thomas Hansen |2018 年 11 月

我喜欢我C#编译器。我女朋友声明我喜欢多个我喜欢她,尽管出于显而易见的原因,我将永远不会承认该在 public。强类型化,泛型,LINQ,垃圾回收,CLI,在进入的极具吸引力的特征列表。但是,偶尔会需要几天内的关闭从与我编译器通常会提供我喜欢证券。对于那些日子,我选择向动态编程语言中的代码 — 和越动态语言、 更危险和有趣。

嗯。足够的简介。现在让我们来做什么真正的程序员时执行的操作随它去,让我们创建的代码:

世界您好

什么?您认为我所说的"代码"?是,我确实说过,而且该文本实际上是代码。若要了解,来看,此代码可以执行的操作。看一看图 1,它显示了什么我称为符号委托的简单示例。在 Visual Studio 和从代码中的类型中创建空的控制台应用程序图 1

图 1 一个简单示例中的符号的委托

using System;
using System.Collections.Generic;
class MainClass
{
  public static void Main(string[] args)
  {
    // The "programming language"
    var keywords = new Dictionary<string, Action> {
      // The "hello" keyword
      ["hello"] = () => {
        Console.WriteLine("hello was invoked");
      },
      // The "world" keyword
      ["world"] = () => {
        Console.Write("world was invoked");
      }
    };
    // The "code"
    string code = "hello world";
    // "Tokenising" the code
    var tokens = code.Split(' ');
    // Evaluating the tokens
    foreach (var token in tokens) {
      keywords[token]();
    }
  }
}

只需 25 行代码我创建了我自己 micro 的编程语言。若要了解其优点,请注意,可以通过网络; 发送"代码"变量它可以提取从一个数据库;它可以存储在一个文件;我并可以动态更改"你好 world"到"world 你好,"或"你好你好世界 world"就此而言,从字符串完全改变其计算结果的结果。此功能意味着我可以动态合并委托以结尾的函数对象,按顺序评估,根据代码的内容的列表。突然我已变成静态编译的编程语言是动态的脚本语言,只需通过将句子拆分为字词,并使用字典作为查找键的单个单词。中的字典图 1,因此,将成为"编程语言"。 实际上,它是一个符号的委托。

此示例是很明显不吸引人,并仅用来演示的核心理念。我通过找出兔子孔的这些概念进一步创建更吸引人的内容,如中所示图 2

图 2 动态链接委托

using System;
using System.Linq;
using System.Collections.Generic;
public class Chain<T> : List<Func<T, T>>
{
  public T Evaluate(T input)
  {
    foreach (var ix in this)
    {
      input = ix(input);
    }
    return input;
  }
}
class MainClass
{
  public static void Main(string[] args)
  {
    var keywords = new Dictionary<string, Func<string, string>>
    {
      ["capitalize"] = (input) =>
      {
        return input.Replace("e", "EE");
      },
      ["replace"] = (input) =>
      {
        return input.Replace("o", "0");
      }
    };
    string code = "capitalize replace";
    var tokens = code.Split(' ');
    var chain = new Chain<string>();
    chain.AddRange(tokens.Select(ix => keywords[ix]));
    var result = chain.Evaluate("join the revolution, capitalize and replace");
    Console.WriteLine(result);
  }
}

现在,我有些东西看起来几乎很有用,能够动态链在一起的委托,这会导致 lambda 对象的松散耦合的函数。因此该代码声明转换对象按顺序,根据我选择将放入我的代码的符号函数链。对于记录,不应正常继承从列表中,但若要保持示例简短,我决定要执行此操作,用于说明主要思路。

基于这一想法扩展很简单。本练习后,若要查找最小公分可以描述您所知,任何编程构造,它只是碰巧以下委托为泛型委托:

delegate object Function(List<object> arguments);

此委托可以表示曾经发明几乎每个编程结构。计算中的所有内容可以采用输入参数的列表,并返回某些内容返回到调用方。此委托是对所有计算创意的输入/输出基本的定义,变得可用于解决所有计算问题的原子编程结构。

满足 Lizzie

按照我编写这篇文章,我创建了一种编程语言,它体现前面的想法。我编写了整个语言 — 我称为 Lizzie — 我女朋友 Lisbeth 后 — 通过以下几种所有出完整全收周末。在单个程序集中,大约 2,000 行代码完全包含的语言。编译时,它是仅 45 KB 我光盘上,其"编译器"只是 300 行C#代码。Lizzie 还可以轻松实现扩展,并允许任何人都将自己"关键字"添加到它,从而可以轻松地创建自己的域特定语言 (DSL)。这种语言的一个用例是基于规则的引擎,您需要将结合在一起的代码更动态比C#允许。使用 Lizzie 可以将动态脚本代码添加到静态编译C#是图灵完备的功能的代码段的应用程序。Lizzie 设置为C#到你 dinner 是哪些情趣 (英文)。不想要吃仅情趣 (英文),但如果向你 steak 添加一些情趣 (英文),您的体验显然变得更加得心应手。若要尝试出 Lizzie,创建空的控制台应用程序中C#中,将 Lizzie NuGet 包,以及使用中的代码图 3

图 3: 创建域特定语言

using System;
using lizzie;
class Demo1
{
  [Bind(Name = "write")]
  object write(Binder<Demo1> binder, Arguments arguments)
  {
    Console.WriteLine(arguments.Get(0));
    return null;
  }
}
class MainClass
{
  public static void Main(string[] args)
  {
    var code = "write('Hello World')";
    var lambda = LambdaCompiler.Compile(new Demo1(), code);
    lambda();
  }
}

只是 22 行代码中我可以说是已创建我自己的 DSL 和我自己特定于域的关键字添加到语言。

Lizzie 的主要功能是,可以将 Lizzie 代码绑定到上下文类型。中的 LambdaCompiler.Compile 方法图 3实际上,是由其第一个参数会自动推断泛型方法,但其类型参数。在内部,Lizzie 将创建它绑定到您的类型,使具有绑定特性的所有方法可供你从 Lizzie 代码的联编程序。当计算 Lizzie 代码时,它具有一个额外的关键字,名为"写入"。 可以绑定到 Lizzie 代码的任何方法,只要它具有正确的签名。并可以将 Lizzie 代码绑定到任何类型。

Lizzie 具有多个默认关键字,它向您提供有关你自己的代码,但无需使用这些,如果您不希望。图 4显示使用这些关键字的一些更完整示例。

图 4 使用一些默认的关键字

using System;
using lizzie;
class Demo1
{
  [Bind(Name = "write")]
  object write(Binder<Demo1> binder, Arguments arguments)
  {
    Console.WriteLine(arguments.Get(0));
    return null;
  }
}
class MainClass
{
  public static void Main(string[] args)
  {
    var code = @"
// Creating a function
var(@my-function, function({
  +('The answer is ', +(input1, input2))
}, @input1, @input2))
// Evaluating the function
var(@result, my-function(21,2))
// Writing out the result on the console
write(result)
";
    var lambda = LambdaCompiler.Compile(new Demo1(), code);
    lambda();
  }
}

中的 Lizzie 代码图 4首先创建一个名为"我的函数,"函数,然后调用该函数使用两个整数自变量。最后,将写出到控制台函数调用的结果。21 行C#代码和八个 Lizzie 代码行,我已评估的一段中的动态脚本语言创建一个函数的动态代码我C#代码,同时将一个新的关键字添加到我使用的脚本语言。所花费代码只是 33 的行总数,包括注释。这些 33 行代码,可以声明已创建您自己的编程语言。Anders Hejlsberg、 将指针移到漫游,并让小 Jimmy 接管...

是 Lizzie"真正"的编程语言?

若要回答这个问题,需要考虑考虑要实际编程语言。Lizzie 是图灵完备,并至少从理论上讲,允许您解决您可能想到的每个计算问题。因此,根据的编程语言"真实"由什么构成的正式定义,是当然真实作为任何其他编程语言。但是,它具有不解释也不编译,因为每个函数调用是只需进行的字典查找。此外,其特点是只有少量的构造,并所有内容重点围绕"function(arguments)"语法。事实上,即使语句遵循以前在泛型委托中定义的函数语法:

// Creates a function taking one argument
var(@foo, function({
  // Checking the value of the argument
  if(eq(input, 'Thomas'), {
    write('Welcome home boss!')
  }, {
    write('Welcome stranger!')
  }) // End of if
}, @input)) // End of function
// Invoking the function
foo('John Doe')
The syntax of if is as follows:
if(condition, {lambda-true}, [optional] {lambda-else})

"If"关键字的第一个参数是一个条件。第二个参数是 lambda 块,如果该条件将产生非空值 (true),则评估。第三个参数是可选的 lambda 块,如果条件结果将为空 (false),则评估。"If"关键字实际上是向其可以提供 lambda 参数使用一个函数以便"{......代码}"语法来声明你 lambda。这可能会感觉一开始,略有奇怪,因为所有操作都发生在开始标记和关键字,与使用更传统的语法的其他编程语言不同的右括号之间。但是,为了 300 行代码创建编程语言编译器,以粗体显示的一些决策事项只是不得不进行。和 Lizzie 是有关简单性的全部信息。

Lizzie 的函数都非常类似于 s 表达式的结构从 Lisp,尽管不奇怪波兰表示法。因为 s 表达式可以描述任何内容,并且 Lizzie 的函数都只是 s-表达式使用符号 (第一个参数) 之外及其括号可以描述的任何内容与 Lizzie。这可以说是将 Lizzie 变为 Lisp 为公共语言运行时 (CLR),具有更直观语法的动态实现C#/JavaScript 开发人员。它允许您添加基于静态编译的动态代码C#,而无需读取数千个页面的文档,了解新编程语言。事实上,Lizzie 的整个文档仅为 12 页的文本,这意味着在大约 20 分钟内的软件开发人员都可以按原义了解 Lizzie。

Lizzie — 代码的 JSON

Lizzie 的我最喜爱的功能之一是其缺少的功能。我将说明这一点与 Lizzie 不能执行的操作的部分列表。Lizzie 不能:

  • 读取或写入的文件系统
  • 执行 SQL 查询
  • 要求您提供您的密码
  • 在所有更改您的计算机的状态

事实上,一段 Lizzie 代码在初始状态下不能为恶意的行为,甚至不在理论上 !这缺少的功能提供 Lizzie 一些独特功能,Roslyn 和C#脚本不提供。

在其原始状态下,Lizzie 是完全安全的您可以安全地传输代码通过网络,从一台计算机向另一台计算机,将使用 JSON 来传输数据的相同方式。然后在接受 Lizzie 代码您终结点,必须显式实现的任何函数代码,您需要 Lizzie 具有访问权限,以便使其为你的用例的支持。这可能包括C#从 SQL 数据库或更新 SQL 数据库中的数据或者要读取或写入文件的功能读取数据的方法。但是,所有这些函数调用可能会推迟到您已确信尝试执行任何尝试执行的操作的代码实际上允许执行此操作。因此,您可以轻松实现身份验证和授权之前允许为,例如"insert sql,""读取的文件"或任何其他操作。

Lizzie 此属性允许您创建通用的 HTTP REST 终结点的客户端层发送到您的服务器,然后在其中计算 Lizzie 代码的位置。然后,您可以创建一个 JSON 响应,它将发送回客户端服务器。和更有意思的是,可以实现此安全。可以实现接受仅包含 Lizzie 代码的 POST 请求的单个 HTTP REST 终结点,并按原义使用 100%动态和泛型 Lizzie 计算器替换整个后端。这样,您可以移动整个业务逻辑和数据访问层到前端代码中,以便将前端代码动态创建 Lizzie 代码,它将传输到服务器进行评估。并且你可以执行此操作,安全地假设身份验证和授权你的客户端,然后再允许其评估 Lizzie 代码。

基本上,整个应用程序,突然、 轻松地构建在 JavaScript 或 TypeScript 或 ObjectiveC 或任何内容,并可以生成动态喜欢的任何编程语言创建 Lizzie 代码中的客户端并将此代码发送到你的服务器。

从 Einstein 获得的教训

当 Albert Einstein 记下其著名的等式来说明我们的世界时,它必须只有三个简单的组件: 能源、 批量和光速平方值。公式可以轻松地理解的旧的数学相当不错掌握任何 14 年。了解计算机编程,但是,需要数千个页面和数以百万计否则数万亿个字,首字母缩写词,令牌,且符号,整个维基百科部分上范例,大量的设计模式和多种语言,以及每个使用完全不同的结构和创意,都要求"重要"框架和库需要先添加到"解决方案",然后可以在你的域问题上开始工作。并且,你应知道所有这些技术的核心然后才能开始作为中间软件开发人员引用自己。我唯一可以看到此处的问题的人?

Lizzie 不是万能和既不是符号委托,而这些图标确实"20 转到 10。"的方向中的步骤 有时,若要向前移动,您需要先来看一个步骤返回。有时需要 neutrally 从外部观察您自己。如果我们,为专业人员社区,这样做,我们只是可能会意识到,我们当前的理由的解决方法是为了简单起见和不 50 多个设计模式,15 新查询语言、 100 的新语言功能和一百万个新的库和框架,每个都有万亿移动部件。

不是详细信息,往常一样,因此为我提供的详细信息,也越详细信息 !如果你想更少,我在一起github.com/polterguy/lizzie。这就是在这里可找到 Lizzie,零类型安全、 与任何关键字,任何运算符、 没有 OOP,绝对不得那么多的单一库或框架的迹象。

总结

大部分计算行业往往不同意我的想法的大多数。如果您要求平均软件构建了他认为有关这些概念,他可能会作为源,以证明我是如何错误引发整个库中将人脸。例如,软件开发的流行假设条件是强类型化是很好,弱类型化已损坏。对我来说,但是,简单是仅游戏闪亮登场,即使它需要引发窗外强类型化。但请记住,、 Lizzie 等想法旨在"的情趣"您现有的静态类型化C#的代码而不是替换它。即使你永远不会使用本文中介绍直接了解关键概念可帮助你编写传统的编程语言中的标准代码更有效地编码想法并帮助你将用于实现的简单性。

编程历史经验

返回当我是初级开发人员,我用于创建第 3 层应用程序。想法是单独的业务逻辑层从数据层和 UI 层。问题在于,因为前端框架越来越复杂,您被迫创建 6 层应用程序的体系结构。首先需要创建的第 3 层的服务器端体系结构,然后第 3 层客户端的体系结构。然后,这似乎还不够,您必须要将代码移植到 Java、 ObjectiveC 或任何支持所有可能的客户端那里。很抱歉,但这种类型的体系结构在这里,为钝是我们所说的"我们驱动设计"因为它的需求的应用程序的复杂性增加到所处位置通常几乎无法维护的点。前端 UI 中的单个更改通常通过 15 层体系结构和四个不同的编程语言进行传播,会强制您在所有这些层只是为了将简单列添加到前端中的数据网格中进行更改。Lizzie 能解决这个问题,从而在前端将代码发送到后端,该后端计算并返回到客户端以 JSON 形式。当然,您会丢失类型安全,但类型安全性为代价的无需将结合在一起的不同层的应用程序,以便在一个位置的更改会传播到您的项目中的所有其他层,类型安全的成本时,只是不值得。

我开始编写代码时我 8 岁,在 1982年 Oric 1 上。显然,我还记得我编写了第一个计算机程序。它了如下:

10 PRINT "THOMAS IS COOL"
20 GOTO 10

如果 8 岁的儿童现在想要重现此经验,遵循所有最佳做法,使用 Angular 之类的客户端框架和等.NET 后端框架,此 kid 可能需要知道数千个页面的技术的计算机科学通过核心宣传资料。与此相反,我先了约 300 页和少量计算机杂志的一本书。做为 100 页之前,我已创建我自己计算机的游戏,在 8 的年龄。很抱歉: 如果这让我听起来很旧,但这不是发展和改进,这是"委托"和疯狂的。并且,是的开始 frenetically 写下您异议之前,此问题已科学研究,和 neutrally 观察到和确认,通过教授和博士,所有更聪明。

这一事实是该计算机编程 (人类) 标准在增加废弃,因为不断增加的复杂性可能会很快达到它可以超越了人类大脑的容量来创建计算机程序的点。编程成为从认知角度来看,因此满足严苛,因为它可能没有咖啡因将能够执行此操作过程中可能的 10 年。同时,人类变得越来越依赖于计算机和软件每一天。呼叫我旧 fashioned 在这里,但我一样,有人正在太空是能够了解我的快乐、 存在和生命周期质量而言至关重要的内容的想法。


Thomas Hansen 自 8 岁起便一直在开发软件,他于 1982 年就开始使用 Oric-1 计算机编写代码。他将称为的处理计算机编程人员试图减少现代编程的复杂性,从而拒绝喜欢技术 dogmas 中的任何相信自己。Hansen 适用于 Bright 塞浦路斯他在其中创建金融科技软件中的代码。

衷心感谢以下 Microsoft 技术专家对本文的审阅:James McCaffrey


在 MSDN 杂志论坛讨论这篇文章