首页 > 解决方案 > 泛型方法中的可空类型

问题描述

我正在学习 Dart,我希望有一种类似于letKotlin 的方法。

我想将其用作:

var variable = ...;// nullable type, for example MyClass?
var test1 = let(variable, (it) => 'non null: ${it.safeAccess()}');
// test1 type is String?
var test2 = let(variable, (it) => 'non null: ${it.safeAccess()}', or: () => 'Default value');
// test2 type is String since either way we return a String

在此示例中,变量是 的可空实例,MyClass如果未提供回退,则输出为可空字符串,如果提供非空回退,则输出为非空字符串。

这是我写的原型:

typedef O LetCallback<I, O>(I value);
typedef O OrCallback<O>();

O let<I, O>(I? value, LetCallback<I, O> cb, {OrCallback<O>? or}) {
  if (value != null) {
    return cb(value);
  }
  if (or != null) {
    return or();
  }
  if (null is O) {
    return null;
  }
  throw Exception("Please provide a default non-null value");
}

Dart 抱怨我不能返回 null,但我不明白为什么它是非法的。我曾预料到这一点(以明确的语法):

var variable = ...;// nullable type, for example MyClass?
var test1 = let<MyClass, String?>(variable, (it) => 'non null: ${it.safeAccess()}');
// I=MyClass, O=String?
var test2 = let<MyClass, String>(variable, (it) => 'non null: ${it.safeAccess()}', or: () => 'Default value');
// I=MyClass, O=String

在我的预期中,编译器会将类型推断OString?or String,因此return null只有当O它可以为空时才是合法的。

似乎使用通用语法,引用的类型总是不可为空的。是这样吗?是语言的限制吗?是否可以编写我想要实现的目标,或者我是否被迫有两个实现?(例如letletNotNull例如)


编辑:写完之后,我尝试了两种实现路线。这是我写的:

typedef O LetCallback<I, O>(I value);
typedef O OrCallback<O>();

O letNonNull<I, O>(I? value, LetCallback<I, O> cb, OrCallback<O> or) {
  if (value != null) {
    return cb(value);
  }
  return or();
}

O? let<I, O>(I? value, LetCallback<I, O> cb, {OrCallback<O>? or}) {
  if (value != null) {
    return cb(value);
  }
  if (or != null) {
    return or();
  }
}

出于某种原因,这是合法的:

var test = letNonNull(null, (it) => "whatever", () => null)

我曾预计() => null回调将是编译器错误,因为O不能为空(根据我最初的观察:我不能返回 null)。

似乎没有完全执行零安全性。

编辑2:似乎只有推断出类型才是合法的。例如:

letNonNull(null, (it) => "bogus", () => null); // legal
letNonNull<String, String>(null, (it) => "bogus", () => null); // illegal

我曾希望推断的类型是非空的......

标签: dart

解决方案


您无法返回,null因为O可能会绑定到不可为空的类型。类型变量并非“始终不可为空”,但它们始终可能不可为空。从具有返回类型的函数返回的内容O必须对 的所有可能绑定有效O,即使它绑定到不可为空的类型也是如此。或者当它绑定到Never. 这意味着唯一可能有效返回的类型是O它自己,并且null没有 type O

如果您希望始终能够返回null,则必须将返回类型let设为 be O?。这使得它始终可以为空,即使O它本身不可为空。在这种情况下,我会O通过给它一个extends Object.

与您一样,另一种方法是返回nullifnull是有效的返回值,如果不是则抛出(从而避免返回任何内容,因为您没有要返回的内容),但是您的方法不适用于该类型系统。尝试改变

 if (null is O) {
    return null;
  }

  O? nullReturn = null;
  if (nullReturn is O) {
    return nullReturn;
  }

如上所述,您可以返回的唯一类型是O,因此您希望该值null具有 type O。您可以这样做if (null is O) return null as O;(或者甚至只是return null as O;依靠TypeError演员阵容而不是自己投掷),或者您可以像这个例子一样使用类型提升来避免额外的as.

您可能还想I使用绑定限制为不可为空,然后I?将参数用于let,但不用于cb。这确保了推断的I类型始终不可为空。

O let<I extends Object, O>(
    I? value, 
    O Function(I) cb, 
   {O Function()? or}) {
  if (value != null) {
    return cb(value);
  }
  if (or != null) {
    return or();
  }
  O? returnNull = null;
  if (returnNull is O) {
    return returnNull;
  }
  throw ArgumentError.value(null, "or", 
      "Please provide a default non-null value");
}

letNotNull还需要对类型变量进行限制:

O letNonNull<I extends Object, O>(
    I? value, O Function(I) cb, O Function() or) {
  if (value != null) {
    return cb(value);
  }
  return or();
}

原因

var test = letNonNull(null, (it) => "whatever", () => null)

有效的是它推断出letNotNull<Object, String?>,并且

letNonNull<String, String>(null, (it) => "bogus", () => null); // illegal

无效,因为 for 的类型O不可为空。

类型系统不知道该or函数参数将如何使用,它只是检查它是否是提供O Function()的实际值的参数类型的正确子类型O。函数体的类型检查确保它只能用于结果可接受的位置。这就是null上面不允许返回的类型检查,因为该检查必须适用于所有O可以绑定的类型。

我会考虑将let操作定义为扩展方法,因为它会先对值进行类型推断,然后再查看回调。就像是:

extension Let<T extends Object> on T? {
  R let<R>(R Function(T) callback, {R Function()? or}) {
    var self = this;
    if (self != null) return callback(self);
    if (or != null) return or();
    R? nullReturn = null;
    if (nullReturn is R) return nullReturn;
    throw ArgumentError.notNull("or");
  }
}

推荐阅读