迎接Dart 2.5时代的来临

发表时间: 2019-09-18 13:20

由Google(谷歌)公司开发的Dart语言迎来了2.5版本的更新。本次更新提供了ML Complete(由机器学习驱动的代码补全功能)和dart:ffi 外部函数接口(用来直接从 Dart 调用 C 语言代码)。

Dart 2.5

类型化编程语言的核心优势之一,就是在类型中附带的信息使得 IDE / 编辑器能够在键入代码时提供强大的代码补全功能,从而帮助开发者提高效率。通过代码补全,开发者只需要输入代码的开头部分即可从提供的选项中进行选择,从而避免拼写错误,也便于探索各种 API。

但随着 API 数量的增长,探索 API 也变得愈发困难,因为补全功能提供的列表太长,开发者无法按照字母顺序去逐一浏览。在过去的一年里,我们一直在努力让机器学习来解决这个问题。简单地讲,我们通过分析 GitHub 上大量开源的 Dart 代码来训练一个模型,用以分析特定上下文时不同代码成员的出现模式。这个基于 TensorFlow Lite 打造的模型在被训练成型后,可以在开发者编写代码时预测接下来需要用到的代码内容。这个新功能我们称之为 ML Complete。以下是使用 Flutter 框架开发新的 MyHome widget 的示例:

使用 ML Complete 开发 Flutter widget 时的示例

  • 用于分析的大量 GitHub 开源 Dart 代码
  • https://console.cloud.google.com/marketplace/details/github/github-repos
  • TensorFlow Lite
  • https://www.tensorflow.org/lite

让我们来深入了解一下它的运行机制。假设您正在编写一个小程序来计算从当前时间开始一天后的时间。使用 ML Complete,您将获得下图这样迅捷的开发体验。

使用 ML Complete 编写代码的体验

不使用 ML Complete 编写同样代码的体验

首先,请注意 ML Complete 会根据开发者输入的变量名称 now 自动给出 DateTime.now() 的建议。当第一行输入完成后,请注意我们在开发者输入第二个变量名时,也给出了 tomorrow 这个变量名建议。最后,基于 now 这个变量给出了第二个补全建议 add(…)。而在上图的非 ML Complete 体验中,我们必须手动键入 DateTime,而且在键入 tomorrow 变量名时没有补全提示,另外 now 的 add(…) 方法在推荐列表更下面的位置才出现。

许多开发者要求我们为从 Dart 调用 C 代码提供更好的支持。一个非常明确的信号,是在 Flutter 问题反馈专区里 C 语言互操作是呼声最高的功能请求,得票数超过 600。这些功能请求背后有许多有趣的用例,包括调用低级平台 API (如 stdlib.h 或 Win32),调用现有的跨平台库以及用 C 语言编写的实用程序 (如 TensorFlow、Realm 和 SQLite) 等。

  • Flutter 功能请求列表
  • https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc
  • stdlib.h
  • https://pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html
  • Win32
  • https://en.wikipedia.org/wiki/Windows_API

目前,直接从 Dart 调用 C 的支持仅限于使用原生扩展与 Dart VM 进行深度集成。或者,Flutter 应用可以通过平台通道调用 host,并从那里来间接调用 C。这种两层的间接调用表现并不理想,我们希望提供一种新的机制,能够提供出色的性能,易于使用,并可以在许多支持 Dart 的平台和编译器上运行。

  • 原生扩展
  • https://dart.dev/server/c-interop-native-extensions
  • 平台通道
  • https://flutter.dev/docs/development/platform-integration/platform-channels
  • 支持 Dart 的平台和编译器
  • https://dart.dev/platforms

Dart-C 互操作支持两种主要场景:

  • 在操作系统 (OS) 上调用基于 C 的系统 API
  • 调用基于 C 的代码库,该代码库可以基于单个操作系统,也可以是跨平台的

我们来看看第一个互操作场景。我们将调用 Linux 命令 system,它可以执行任何系统命令; 传递给它的参数实际上是传递给了 shell/terminal,并在那里运行。这个指令的 C 语言头部如下所示:

// C header: int system(const char *command) in stdlib.h

任何互操作机制的核心挑战都是处理两种语言的语义差异。在 dart:ffi 这里,Dart 代码需要处理好两件事:

  1. C 语言函数及其参数的类型,以及返回类型
  2. 与之对应的 Dart 函数及其类型

我们通过定义两个 typedef 来做到这一点:

// C header typedef:typedef SystemC = ffi.Int32 Function(ffi.Pointer<Utf8> command);// Dart header typedef:typedef SystemDart = int Function(ffi.Pointer<Utf8> command);

下面我们需要加载代码库,并查找我们要调用的函数。具体做法取决于操作系统,在下面这个例子中,我们使用的是 macOS。

// Load `stdlib`. On MacOS this is in libSystem.dylib.final dylib = ffi.DynamicLibrary.open('/usr/lib/libSystem.dylib');// Look up the system function.final systemP = dylib.lookupFunction<SystemC, SystemDart>('system');

您可以在 GitHub 上找到可供所有三种操作系统 (macOS、Windows、Linux) 执行的完整示例。

  • macOS 示例
  • https://github.com/dart-lang/samples/blob/master/ffi/system-command/macos.dart
  • Windows 示例
  • https://github.com/dart-lang/samples/blob/master/ffi/system-command/windows.dart
  • Linux 示例
  • https://github.com/dart-lang/samples/blob/master/ffi/system-command/linux.dart

接下来,我们使用与特定操作系统相关的编码对字符串参数进行编码,调用该函数,并再次释放参数内存:

// Allocate a pointer to a Utf8 array containing our command.final cmdP = Utf8.toUtf8('open http://dart.dev');// Invoke the command.systemP(cmdP);// Free the pointer.cmdP.free();

这段代码会执行系统命令,使用系统默认浏览器打开 dart.dev 网页:

通过 dart:ffi 使用系统 API 打开默认浏览器。

关注【GeekYawei】,获取更多开发相关资讯。