65.9K
CodeProject 正在变化。 阅读更多。
Home

HOPE - 高阶编程环境

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (40投票s)

2014年5月25日

CPOL

16分钟阅读

viewsIcon

67529

动态运行时语义计算环境

观看视频!(另请参阅 GitHub 页面获取更多视频链接)

引言

在我写这篇文章的时候,明天将是我在 Code Project 上发表第一篇文章的 12 周年纪念日:有机编程环境 (OPEN)。这个概念,即一个开放的表面,人们可以在上面放置数据,然后数据会触发进程(也是运行时引入的),是我偶尔会重新审视的一个概念。最初用 C++ 编写的 OPEN 不具备以下优势:

  • 反射
  • 运行时编译
  • 和动态类型

这些在当今许多语言中都可用。这些是将 OPEN 带入当今世界的基石,在 C# 中重新实现为 HOPE。主要特点是:

  • 可以拖放到表面的“接收器”组件(程序集)
  • 与数据相关联的语义
  • 代表数据“协议”和“信号”(数据和语义)的“载体”
  • 一个可视化系统,通过载体动画化接收器之间的交互

那么 HOPE 是什么?它是一个动态运行时语义计算环境。

计算语义学

HOPE 实现的是一个概念空间(参见附录 B),用于“自动化构建和推理意义表示的过程”。此外,“单个单元的意义可以组合起来形成更大单元的意义。”

演示

本次发布提供了三个演示

  1. 一个简单的“Hello World”心跳
  2. 一个图片到缩略图的转换器和查看器
  3. “今天的天气”,使用 .NET 的语音合成器播报当前天气状况。

致谢

我复活 OPEN 并创建 HOPE 系统的兴趣,要归功于我的朋友,Eric Harris-BraunArthur Brock。Eric 和 Arthur(以及其他人)正在从事一个项目(Metacurrency),其架构(Ceptr)是 HOPE 的灵感来源。可以说他们正在研究底层架构,而 HOPE 是一个展示他们概念的高级原型。当然,吸引我立即加入他们项目的原因之一是,其核心实现了我通过 OPEN 引入的概念。由于我也作为“文档员”参与他们的项目,因此该项目中的其他一些内容可能会出现在 Code Project 上,当然也会出现在其他地方。

术语

我已经介绍的术语借鉴了 Eric 和 Arthur 的“Ceptr”项目(“一个用于去中心化自描述协议栈的计算平台”)。我并不声称他们的术语与这里的概念有直接映射,但它们在高层次上足够相似,以至于我觉得任何阅读 Ceptr 文章的人都能够立即理解这里使用相同术语的含义。反之亦然,因为这实际上是第一篇关于架构概念的文章,我希望这篇文章能成为引入您将在后续文章中遇到的术语的过渡。

以下是我将使用的术语及其定义

接收器系统

负责管理接收器集合(见下文)并进行消息传递的程序集。

接收器

一个 .NET 程序集,其中包含一个实现 IReceptorInstance 的类,允许它向接收器系统注册自己,声明它对哪些载体(见下文)感兴趣。当接收到载体时,接收器会激活一个工作进程(在同一线程或单独线程上),其结果是内部状态改变、发出一个或多个载体、或执行某些“边缘”进程,或上述各项的某种组合。

边缘接收器

一个与外部世界(即计算机平台的其余部分和操作员(通过视听方式))交互的接收器。

协议

一组(或流)数据的定义。协议决定了数据的位置。在一个缺乏语义信息的流中,协议提供了一种将流解析为语义数据——具有意义的数据——的方法。在这个原型中,协议是声明式(在 XML 中)确定的,并且是完全语义化的。

信号

协议是关于如何解析值的声明,而信号是实际的值。

载体

接收器之间的消息传递构造。载体实现为一个对象,由协议和信号(见下文)组成,以便信号可以映射到“意义”。XML 本身就是一个很好的载体示例——它既包含协议又包含信号。元素具有有意义的名称,属性-值对指定了值的意义。此外,借助 XSD,可以进一步指定元素或属性的语义,描述关系、顺序等。

可视化

在系统执行计算时,将数据、过程和流程可视化,不仅美观,还提供了一个具体的界面,可以与系统进行交互。

  • 接收器(程序集)可以从 Explorer 拖放到表面
  • 预设的载体也可以拖放到表面上
  • 存在于表面但目前没有接收器的载体可以很容易地可视化
  • 可以进行额外的渲染以方便用户体验。

由于 HOPE 具有视觉特性,因此将会有视频链接来展示它(即使在初始状态下)能够实现的功能。

接收器

我们将简要介绍一下受体。

样板

每个接收器都需要实现 IReceptorInstance。接收器的样板代码如下所示

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using Clifton.Receptor.Interfaces;
using Clifton.SemanticTypeSystem.Interfaces;

namespace AReceptor   // Name this as you wish
{
  // The class name is completely arbitrary
  public class ReceptorDefinition : IReceptorInstance
  {
    // The Name property is used by the visualizer.
    public string Name { get { return "Heartbeat"; } }

    // Also used by the visualizer to distinguish edge receptors.
    public bool IsEdgeReceptor { get { return false; } }

    // Prevents the receptor from being rendered at all.
    public bool IsHidden { get { return false; } }

    // Your interface to the receptor system.
    protected IReceptorSystem rsys;

    // Constructor.  You will be passed the IReceptorSystem instance.
    public ReceptorDefinition(IReceptorSystem rsys)
    {
      this.rsys = rsys;
      // Do your receptor-specific initialization here.
    }

    // Called when the receptor is removed or the application is closed.
    public void Terminate()
    {
      // Stop any worker threads, dispose unmanaged objects, etc.
    }

    // Returns a list of carrier names that this receptor wants to monitor.
    public string[] GetReceiveCarriers()
    {
      return new string[] {};
    }

    // Processes a carrier.
    public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
    {
    }
  }
}

需要改进的地方

在当前的实现中,载体名称是字面字符串(不好),并且如果接收器处理多个载体,则需要检查(也差)。

此外,协议存储在一个单独的 XML 文件中。理想情况下,发出载体的接收器应该定义协议。

心跳接收器——一个例子

这里我们有一个“心跳”接收器。在上面的样板代码的基础上,我们添加

public ReceptorDefinition(IReceptorSystem rsys)
{
  this.rsys = rsys;
  InitializeRepeatedHelloEvent();
}

public void Terminate()
{
  timer.Stop();
  timer.Dispose();
}

protected void InitializeRepeatedHelloEvent()
{
  timer = new Timer();
  timer.Interval = 1000 * 2; // every 10 seconds.
  timer.Tick += SayHello;
  timer.Start();
}

protected void SayHello(object sender, EventArgs args)
{
  ISemanticTypeStruct protocol = rsys.SemanticTypeSystem.GetSemanticTypeStruct("DebugMessage");
  dynamic signal = rsys.SemanticTypeSystem.Create("DebugMessage");
  signal.Message = "Hello World!";
  rsys.CreateCarrier(this, protocol, signal);
}

此接收器不侦听任何载体,它只是每两秒发出一个载体。

日志接收器

但是,Logger 接收器侦听包含“DebugMessage”协议的载体

public string[] GetReceiveCarriers()
{
  return new string[] { "DebugMessage" };
}

public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  string msg = signal.Message;
  System.Diagnostics.Debug.WriteLine(msg);

  Flyout(msg);
}

protected void Flyout(string msg)
{
  ISemanticTypeStruct protocol = rsys.SemanticTypeSystem.GetSemanticTypeStruct("SystemMessage");
  dynamic signal = rsys.SemanticTypeSystem.Create("SystemMessage");
  signal.Action = "Flyout";
  signal.Data = msg;
  signal.Source = this;
  rsys.CreateCarrier(this, protocol, signal);
}

此接收器确实会做一些事情——它将信号内容(在本例中是消息)输出到调试流,并创建一个载体,指示应用程序创建消息的弹出动画。

幕后

请注意,信号类型为 dynamic。为了支持 HOPE 的宗旨,“动态运行时语义计算环境”,协议(通常)不能硬编码。我们不需要预先知道载体的协议,接收器也无需关心协议是否会扩展。dynamic 关键字允许我们以简写方式编写必要的代码,以根据协议反射信号。是的,这意味着我们正在对载体信号进行鸭子类型。有关语义类型系统的一些讨论在附录 C 中描述。

载体

载体是微不足道的。它们描述了协议和信号实例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Clifton.Receptor.Interfaces;
using Clifton.SemanticTypeSystem.Interfaces;

namespace Clifton.Receptor
{
  public class Carrier : ICarrier
  {
    public ISemanticTypeStruct Protocol { get; set; }
    public dynamic Signal { get; set; }

    public Carrier(ISemanticTypeStruct protocol, dynamic signal)
    {
      Protocol = protocol;
      Signal = signal;
    }
  }
}

请注意协议如何是语义类型。

接收器系统

接收器系统管理接收器和载体创建。当前的实现旨在与可视化器(当然是间接的)协同工作,这样当载体与接收器配对时执行的动作将交给可视化器调用,当载体通过可视化器用于动画载体的任何机制到达其目的地时。

protected Action GetProcessAction(IReceptorInstance from, Carrier carrier, bool stopRecursion)
{
  Action action = new Action(() =>
  {
    var receptors = CarrierReceptorMap[carrier.Protocol.DeclTypeName];
    int numReceptors = receptors.Count;

    receptors.ForEach(receptor =>
    {
      Action process = 
      new Action(() => receptor.Instance.ProcessCarrier(carrier.Protocol, carrier.Signal));

      if (receptor.Instance.IsHidden)
      {
        // Don't visualize carriers to hidden receptors.
        process();
      }
      else if (!stopRecursion)
      {
        ISemanticTypeStruct protocol = 
                 SemanticTypeSystem.GetSemanticTypeStruct("CarrierAnimation");
        dynamic signal = SemanticTypeSystem.Create("CarrierAnimation");
        signal.Process = process;
        signal.From = from;
        signal.To = receptor.Instance;
        signal.Carrier = carrier;
        CreateCarrier(null, protocol, signal, 
                      receptor.Instance.GetReceiveCarriers().Contains("*"));
      }
    });
  });

  return action;
}

请注意,当接收器隐藏时,操作会立即调用,因为没有可视化。另请注意,如何创建一个载体供应用程序接收(应用程序本身就是一个接收器!),用于动画化载体。Lambda 表达式和闭包的力量!

stopRecursion 是必需的,以阻止通配符接收器无休止地接收自己的消息。

在某个时候,这一切都将被重构,这样接收器系统甚至都不会知道是否有可视化器正在注入到进程中。

载体排队

该系统的另一个方面是,如果没有接收器,载体就会累积。这可能是好的,也可能是坏的,取决于所需的进程,目前还没有一种机制来指示新创建的载体是否应该替换现有载体。上面的动画演示了一个累积了未处理载体的接收器,以及当我们向表面放置一个能够处理这些载体(一个程序集)的接收器时会发生什么。

protected void ProcessReceptors(IReceptorInstance from, Carrier carrier, bool stopRecursion)
{
  Action action = GetProcessAction(from, carrier, stopRecursion);
  List<Receptor> receptors;

  bool haveCarrierMap = CarrierReceptorMap.TryGetValue
                        (carrier.Protocol.DeclTypeName, out receptors);
 
  // If we have receptors for this carrier 
  // (a mapping of carrier to receptor list exists and receptors actually exist)...
  if (haveCarrierMap && receptors.Count > 0)
  {
    // Perform the action.
    action();
  }
  else
  {
    // Othwerise, queue up the carrier for when there is a receptor for it.
    queuedCarriers.Add(new CarrierAction() { Carrier = carrier, Action = action });
  }
}

在这里,如果在创建载体时没有接收器对接收带有指定协议的载体感兴趣,我们就会将该操作排队。

所有这一切中一个有趣的点是,当载体创建时

public void CreateCarrier(IReceptorInstance from, 
                          ISemanticTypeStruct protocol, 
                          dynamic signal, 
                          bool stopRecursion = false)
{
  Carrier carrier = new Carrier(protocol, signal);
  NewCarrier.Fire(this, new NewCarrierEventArgs(from, carrier));

  // We stop recursion when we get to a wild-card carrier receptor, otherwise, 
  // the "Carrier Animation" message recursively creates 
  // additional messages to the wild-card receptor.
  ProcessReceptors(from, carrier, stopRecursion);
}

它是在本地创建的,但通过闭包机制,我们可以在以后处理该实例。另请注意,可视化器会挂钩 NewCarrier 事件,以便它可以显示这些载体在生成它们的接收器周围积累。

图片缩略图创建器和查看器示例

提供了三个接收器,用于方便地创建拖放到表面上的 JPG 图像的缩略图

  1. ImageWriterReceptor - 将缩略图写入同一位置,文件名后附加“-thumbnail”文本。
  2. ThumbnailCreatorReceptor - 接收带有 JPG 图像协议的载体,并将其转换为更小的图像
  3. ThumbnailViewerReceptor - 将缩略图创建器的载体输出重新解析为可视化器(应用程序的一部分)处理的载体。

您在这里看到的是三张图片被拖放到表面上。它们被打包成载体并由缩略图创建器接收。有两个接收器对缩略图图片感兴趣,一个是查看器,另一个是文件写入器,我们看到缩略图图片的载体向这些载体的接收器矢量化。缩略图查看器在查看器接收器周围的半径内显示图片。这里需要注意的是,载体没有被克隆,因此对载体信号进行操作的进程应该意识到其他进程可能同时对它们进行操作。例如,由于图像不是线程安全的,因此您不能在 GDI+ 中渲染它并同时将其保存到磁盘。多线程的整个问题在这种架构中确实变得非常有趣。

拖放

拖放到表面上的图像文件会创建具有特定协议的载体

// Create carriers for each of our images.
ISemanticTypeStruct protocol = Program.SemanticTypeSystem.GetSemanticTypeStruct("ImageFilename");
dynamic signal = Program.SemanticTypeSystem.Create("ImageFilename");
signal.Filename = fn;
// TODO: The null here is really the "System" receptor.
Program.Receptors.CreateCarrier(null, protocol, signal);

缩略图创建器接收器

此接收器处理图像并创建带有缩略图协议的载体

public string[] GetReceiveCarriers()
{
  return new string[] { "ImageFilename" };
}

public async void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  string fn = signal.Filename;
  Image ret = await Task.Run<Image>(() =>
  {
    Bitmap bitmap = new Bitmap(fn);
    // Reduce the size of the image. 
    // If we don't do this, scrolling and rendering of scaled images is horrifically slow.
    Image image = new Bitmap(bitmap, 256, 256 * bitmap.Height / bitmap.Width);
    bitmap.Dispose();

    return image;
  });

  OutputImage(fn, ret);
}

protected void OutputImage(string filename, Image image)
{
  ISemanticTypeStruct protocol = 
           rsys.SemanticTypeSystem.GetSemanticTypeStruct("ThumbnailImage");
  dynamic signal = rsys.SemanticTypeSystem.Create("ThumbnailImage");
  // Insert "-thumbnail" into the filename.
  signal.Filename = filename.LeftOfRightmostOf('.') + "-thumbnail." + 
                    filename.RightOfRightmostOf('.');
  signal.Image = image;
  rsys.CreateCarrier(this, protocol, signal);
}

缩略图查看器接收器

此接收器将图像传递给系统的可视化器。它所做的只是重新引导载体,有点像一个中间神经元。

public string[] GetReceiveCarriers()
{
  return new string[] { "ThumbnailImage" };
}

public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  Image image = signal.Image;
  ShowImage(image);
}

protected void ShowImage(Image image)
{
  // All we do here is send a message to the system 
  // so that it displays our thumbnails as a carousel around or receptor.
  ISemanticTypeStruct protocol = 
           rsys.SemanticTypeSystem.GetSemanticTypeStruct("SystemShowImage");
  dynamic signal = rsys.SemanticTypeSystem.Create("SystemShowImage");
  signal.From = this;
  signal.Image = image;
  rsys.CreateCarrier(this, protocol, signal);
}

缩略图写入器接收器

public string[] GetReceiveCarriers()
{
  return new string[] { "ThumbnailImage" };
}

public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  string fn = signal.Filename;
  Image img = signal.Image;

  img.Save(fn);
}

今日天气示例

这里我们有几个接收器。因为网络服务调用需要一段时间,我还没有创建动画 GIF。此外,由于天气是语音播报的,除了载体四处飞舞之外,并没有太多可“看”的。

不过,有两个显著的要点。第一,我们可以将一个载体作为 XML 片段拖放到表面上

<Carriers>
  <Carrier Protocol="Zipcode" Value="12565"/>
</Carriers>

这是一种向系统注入信号的有用方式。邮政编码由天气服务和邮政编码服务接收器接收,它们调用网络服务来获取信息。例如,邮政编码服务处理邮政编码信号并发出带有位置协议和信号的载体

public async void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  Tuple<string, string> location = await Task.Run(() =>
  {
    string city = String.Empty;
    string stateAbbr = String.Empty;

    try
    {
      string zipcode = signal.Value;
      USZip zip = new USZip();
      XmlNode node = zip.GetInfoByZIP(zipcode);
      XDocument zxdoc = XDocument.Parse(node.InnerXml);
      city = zxdoc.Element("Table").Element("CITY").Value;
      stateAbbr = zxdoc.Element("Table").Element("STATE").Value;
    }
    catch (Exception ex)
    {
      // TODO: Log exception.
      // Occasionally this web service will crash.
    }

    return new Tuple<string, string>(city, stateAbbr);
  });

  Emit(signal.Value, location.Item1, location.Item2);
}

protected void Emit(string zipCode, string city, string stateAbbr)
{
  ISemanticTypeStruct protocol = rsys.SemanticTypeSystem.GetSemanticTypeStruct("Location");
  dynamic signal = rsys.SemanticTypeSystem.Create("Location");
  signal.Zipcode = zipCode;
  signal.City = city;
  signal.State = "";
  int idx = Array.IndexOf(abbreviations, stateAbbr);

  if (idx != -1)
  {
    signal.State = abbreviations[idx + 1];
  }

  signal.StateAbbr = stateAbbr;
  rsys.CreateCarrier(this, protocol, signal);
}

注意 webservice 如何使用 Task.Run 方法异步调用,并在完成后,将 Location 协议和信号打包成一个载体。

天气信息接收器接收带有 LocationWeatherInfo 协议的载体,当邮政编码匹配时,它将数据格式化为可以语音播报的方式,并将文本字符串作为 TextToSpeech 接收器监听的载体发送出去。TextToSpeech 接收器很简单

public class ReceptorDefinition : IReceptorInstance
{
  public string Name { get { return "Text To Speech"; } }
  public bool IsEdgeReceptor { get { return true; } }
  public bool IsHidden { get { return false; } }

  protected SpeechSynthesizer speechSynth;
  protected Queue<string> speechQueue;
  protected IReceptorSystem rsys;

  public ReceptorDefinition(IReceptorSystem rsys)
  {
    this.rsys = rsys;

    speechSynth = new SpeechSynthesizer();
    speechSynth.SpeakCompleted += OnSpeakCompleted;
    speechSynth.Rate = -4;
    speechQueue = new Queue<string>();
  }

  public string[] GetReceiveCarriers()
  {
    return new string[] { "TextToSpeech" };
  }

  public void Terminate()
  {
  }

  public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
  {
    string msg = signal.Text;
    Speak(msg);
  }

  protected void OnSpeakCompleted(object sender, SpeakCompletedEventArgs e)
  {
    if (speechQueue.Count > 0)
    {
      string msg = speechQueue.Dequeue();
      speechSynth.SpeakAsync(msg);
    }
  }

  protected void Speak(string msg)
  {
    if (speechSynth.State == SynthesizerState.Speaking)
    {
      speechQueue.Enqueue(msg);
    }
    else
    {
      speechSynth.SpeakAsync(msg);
    }
  }
}

载体导出器

这是一个“监听一切”的接收器,其目的是将所有载体记录为 XML 流。

public string[] GetReceiveCarriers()
{
  return new string[] { "*" };
}

public void Terminate()
{
  XmlWriterSettings xws = new XmlWriterSettings();
  xws.Indent = true;
  xws.OmitXmlDeclaration = true;
  XmlWriter xw = XmlWriter.Create("carrier_output.xml", xws);
  xdoc.WriteTo(xw);
  xw.Close();
}

public void ProcessCarrier(ISemanticTypeStruct protocol, dynamic signal)
{
  XmlNode carrier = xdoc.CreateElement("Carrier");
  carrier.Attributes.Append(CreateAttribute(xdoc, "Protocol", protocol.DeclTypeName));
  carriersNode.AppendChild(carrier);

  Type t = signal.GetType();
  t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ForEach(p =>
  {
      ... etc ....

当这个接收器在表面上时,您会注意到所有您的载体都会发送给导出器接收器和任何其他对载体协议感兴趣的接收器。因此,例如,这里是我将我居住地的邮政编码拖放到表面上时的运行情况

<Carriers>
  <Carrier Protocol="TextToSpeech" Text="Loading receptors." />
  <Carrier Protocol="TextToSpeech" Text="Receptor System online." />
  <Carrier Protocol="TextToSpeech" Text="Loading receptors." />
  <Carrier Protocol="TextToSpeech" Text="Receptors online." />
  <Carrier Protocol="TextToSpeech" Text="Processing carriers." />
  <Carrier Protocol="Zipcode" Value="12565" />
  <Carrier Protocol="Location" Zipcode="12565" 
   City="Philmont" StateAbbr="NY" State="NEW YORK" />
  <Carrier Protocol="WeatherInfo" Zipcode="12565" 
   Summary="Isolated Thunderstorms" High="75" Low="51">
    <Conditions>
      <WeatherCondition Coverage="isolated" Intensity="none" 
       WeatherType="thunderstorms" Qualifier="gusty winds,small hail" />
      <WeatherCondition Additive="and" Coverage="isolated" 
       Intensity="light" WeatherType="rain showers" Qualifier="none" />
    </Conditions>
    <Hazards />
  </Carrier>
  <Carrier Protocol="TextToSpeech" Text="Here is the weather for Philmont NEW YORK. " />
  <Carrier Protocol="TextToSpeech" Text="The low is 51." />
  <Carrier Protocol="TextToSpeech" Text="The high is 75." />
  <Carrier Protocol="TextToSpeech" Text="The weather is: Isolated Thunderstorms." />
  <Carrier Protocol="TextToSpeech" 
   Text="Today there will be isolated thunderstorms gusty winds, 
         small hail and isolated light rain showers." />
  <Carrier Protocol="TextToSpeech" Text="There are no hazards in effect." />
</Carriers>

请注意,我们完全排除了“从”和“到”的信息——这实际上是不相关的。我们感兴趣的是协议和信号,由载体打包。

载体导出器接收器的目的

除了记录系统活动之外,这是一个很棒的调试工具。人们可以轻松地提取特定的载体-协议-信号实例,并通过将该载体拖放到表面上来测试系统。因此,例如,我们无需每次都将邮政编码拖放到表面上来测试天气数据解析为语音的功能,而是可以从特定运行中拖放载体-协议-信号,并反复使用该载体。

结论

这个系统最棒的地方实际上是它的动态性——编写接收器和行为的“低阶”编程风格是多么容易,以及如何将它们集成以实现新的行为。可重用性不仅仅是“链接到库”,而是“将这个包拖放到表面上,让它开始与系统交互”。这提供了一个更丰富、无代码的上下文,以支持无限数量的有趣行为。

仍然有大量的细节需要解决。通过混合低阶和高阶编程风格,我们可以将行为实现为小型、可重用的组件。应该可以编写开放应用程序,可以快速自定义和增强。这是我的 HOPE,哈哈。

HOPE 将回归……

附录 A

例如,我最近在一家商业网站上遇到了这段 Ruby 代码(大大简化了)

def update_value(value_name, new_value)
  persist_change(value_name, new_value, @value)
  @value = new_value
end

def persist_change(name, old, new)
  ...
end

注意新旧参数是如何颠倒的。现在,在像 Ruby 这样的鸭子类型语言中,你很难让运行时对此报错。充其量,你可以创建几个具有特定方法的类,这会导致运行时错误

class OldValue
  property_accessor :old_value
end

class NewValue
  property_accessor :new_value
end

这里,语义必须作为独特的属性访问器方法实现。

在像 C# 这样的强类型语言中,我们可以在编译时报错,因为类本身的语义声明是不同的

public class OldValue<T>
{
  public T Value {get;set;}
}

public class NewValue<T>
{
  public T Value {get;set;}
}

现在,如果在 C# 中,我们编写

void UpdateValue(string name, NewValue<string> val)
{
  PersistChange(name, val, new OldValue<string>() {Value = myName});
  myName = val.Value;  // assuming the "name" we're updating is "name"
}

void PersistChange(string name, OldValue<string, NewValue<string>)
{
  ...
}

这段代码将无法编译,因为我们提供了足够的语义信息,使编译器能够区分旧字符串值和新字符串值。在 F# 中,值的语义可以使用判别联合更容易地表达。Julia 语言的类型系统更支持将语义附加到值。

附录B

OPEN 的一个关键要求是纳入与数据相关的语义。不能仅仅将数字“42”拖放到表面上而没有附加语义。没有语义,计算是不可能的。例如,除非我们知道“42”的含义,否则我们无法对其进行计算。即使在今天,现代计算仍存在两个问题

  1. 值的含义不是值的一部分。
  2. 任何相关的含义都与值分离。

有关简单示例,请参阅附录 A

因此,我断言我们的计算能力因数据不携带其语义而受到严重削弱。换句话说,如果不了解数据的元世界,我们就无法编写在数据呈现给系统时自动对其进行计算的进程。只要我们继续在数据库、博客、网站上生成大量没有附加语义的数据,我们本质上就是在处理无用且“死亡”的信息。语义将这些数据转化为可用的“知识”。这就是 HOPE 所展示的(希望如此!)。

形式语义学

摘自 Julia Hockenmaier、Laura McGarrity、Bill McCartney、Chris Manning 和 Dan Klein 的"形式语义学"

形式语义学包括

  • 词汇语义学:词语的意义
  • 组合语义学:单个单元的意义如何结合形成更大单元的意义

意义并非特指词典条目,而是语义关联。在上面的链接中,“rooster”一词通过与“male”和“chicken”的语义关联获得意义。在组合语义学中,“句子的意义由其词语的意义及其句法组合方式共同决定。”就计算机数据而言,通常是数据的组织方式以及词语(例如“get”或“put”)决定了意义,例如,HTTP 消息的意义。

认知语义学

摘自认知语义学:“认知语义学认为语言是人类更普遍的认知能力的一部分,因此只能描述世界在人们概念空间中的组织方式。这意味着概念世界与真实世界之间存在某种差异。”

计算语义学

引自计算语义学:“计算语义学是研究如何自动化构建和推理自然语言表达式的意义表示的过程。”虽然 HOPE 不是关于自然语言表达式,但它是关于通过小型过程组件和语义数据来构建和推理意义表示。

附录C

语义类型系统的意图是完整地描述类型,使得所有类型都派生自特定语言的基本类型(尽管技术上它们可以派生自实际的比特),并且高阶类型由低阶类型组成。我有一篇单独的文章(待发布)详细讨论了语义类型系统。这里是一个简要的解释。请注意,当前的 HOPE 协议在某种程度上违反了纯粹的概念。

这是基本类型“Noun”,它反映了自身

<SemanticTypeDecl OfType="Noun">
  <!-- The ST has a Name attribute because it's Struct defines it. -->
  <AttributeValues>
    <Attribute Name="Name" Value="Noun"/>
  </AttributeValues>
</SemanticTypeDecl>

<SemanticTypeStruct DeclType="Noun">
  <Attributes>
    <!-- here the attribute Name's value is the implementing field name -->
    <!-- For example, in C#, resulting form is "string Name;" -->
    <NativeType Name="Name" ImplementingType="string"/>
  </Attributes>
</SemanticTypeStruct>

一个语义类型拥有

  • 一个声明
  • 一个结构

在类型“Noun”的情况下,属性“Name”反映在由语言类型“string”实现的本地类型“Name”上。

这是心跳示例中使用的语义类型“DebugMessage”的声明

<SemanticTypeDecl OfType="Noun">
  <AttributeValues>
    <Attribute Name="Name" Value="DebugMessage"/>
  </AttributeValues>
</SemanticTypeDecl>

<SemanticTypeStruct DeclType="DebugMessage">
  <Attributes>
    <NativeType Name="Message" ImplementingType="string"/>
  </Attributes>
</SemanticTypeStruct>

这可以解读为:“我们有一个名为‘DebugMessage’的语义类型,它由一个名为‘Message’的类型组成,该类型由‘string’类型实现。”

理想情况下,这应该被实现为一种层次结构,其中 Message 本身将是一个名为“Text”的语义类型,由“string”实现,而 DebugMessage 将由语义类型“Text”而不是本地类型“string”实现。HOPE 目前描述的协议最终将被重构,以使其与 HOPE 架构背后的语义类型系统更加纯粹。

代码生成

声明语义类型的 XML 被转换为 C# 代码(可在 HOPE 应用程序中查看)。例如,以下是上面描述的语义类型的代码片段

// This code has been generated by the Semantic Type System.
using System;
using System.Drawing;
using System.Collections.Generic;
using Clifton.SemanticTypeSystem.Interfaces;
using Clifton.Receptor.Interfaces;

namespace SemanticTypes
{
  //Implements ISemanticType as the object type and requires Initialize().
  public class Noun : ISemanticType
  {
    //Native types.
    public string Name {get; set;}

    //Constructor. Typically called by reflection.
    public Noun() {}

    //Implements ICreate.
    public void Initialize(ISemanticTypeSystem sts)
    {
      //Native type initializers.
      Name = "Noun";
      //Additional semantic type initializers.
    }
  }

  //Implements ISemanticType as the object type and requires Initialize().
  public class DebugMessage : ISemanticType
  {
    //Native types.
    public string Message {get; set;}

    //Constructor. Typically called by reflection.
    public DebugMessage() {}

    //Implements ICreate.
    public void Initialize(ISemanticTypeSystem sts)
    {
      //Native type initializers.
      //Additional semantic type initializers.
    }
  }
...

随着新类型的识别,应用程序生成代码并编译它们,然后将它们放入类型存储库。然后可以通过对类型系统的请求来实例化所需的类型

dynamic signal = SemanticTypeSystem.Create("SystemMessage");

编译时代码生成

当然,类型甚至在运行时都可以被参与的接收器完全知晓,因此,为此,可以将系统扩展到在单独的程序集中实现接口,这些接口定义了类型的编译时特性。这将消除鸭子类型,但要求生产者和消费者接收器之间达成协议,即类型是预先已知的。对这种编译时“在接收器编码期间”行为的支持将在以后添加。

历史

  • 2014年5月25日:初始版本
HOPE - 高阶编程环境 - CodeProject - 代码之家
© . All rights reserved.