【问题标题】:CompileTimeError: Type arguments in partial instantiations must be instantiated and are therefore not allowed to depend on type parametersCompileTimeError:部分实例化中的类型参数必须被实例化,因此不允许依赖于类型参数
【发布时间】:2019-11-24 20:22:36
【问题描述】:

当我尝试调用 listenEventReducer 时,我收到了这个奇怪的错误。该方法知道类型,但仍然不确定我哪里出错了。

import 'package:test/test.dart';

enum ObjectChangeType { added, modified, removed }

typedef UpdateItem<T extends ListenEventItem> = T Function(T oldItem, T newItem);

T _defaultUpdate<T extends ListenEventItem>(T oldItem, T newItem) => newItem;

abstract class ListenEventItem {
  String get id;

  ObjectChangeType get changeType;
}

abstract class ListenEvent<T extends ListenEventItem> {
  List<T> get data;

  Map<ObjectChangeType, List<T>> get typedData {
    return data.fold(
      <ObjectChangeType, List<T>>{},
      (Map<ObjectChangeType, List<T>> map, T element) {
        map[element.changeType] = (map[element.changeType] ?? <T>[])..add(element);

        return map;
      },
    );
  }
}

Map<String, T> listenEventReducer<T extends ListenEventItem>(Map<String, T> state, ListenEvent<T> action,
    [UpdateItem<T> update = _defaultUpdate]) {
  update ??= _defaultUpdate;
  final Map<String, T> b = Map<String, T>.from(state);

  for (ObjectChangeType type in ObjectChangeType.values) {
    final List<T> data = action.typedData[type];
    if (data == null || data.isEmpty) {
      continue;
    }

    if (type == ObjectChangeType.added) {
      for (T item in data) {
        b[item.id] == item;
      }
    } else if (type == ObjectChangeType.modified) {
      for (T item in data) {
        b[item.id] = update(b[item.id], item);
      }
    } else if (type == ObjectChangeType.removed) {
      final List<String> ids = data.map((T it) => it.id).toList();
      b.removeWhere((String id, _) => ids.contains(id));
    }
  }

  return b;
}

class Data extends ListenEventItem {
  Data(this.id, this.changeType, this.metadata);

  final String metadata;

  @override
  final String id;

  @override
  final ObjectChangeType changeType;
}

class DataEvent extends ListenEvent<Data> {
  DataEvent(this.data);

  @override
  final List<Data> data;
}

void main() {
  final List<Data> items = List<Data>();
  items.add(Data('0', ObjectChangeType.added, 'a'));
  items.add(Data('1', ObjectChangeType.added, 'b'));
  items.add(Data('0', ObjectChangeType.removed, 'a'));
  items.add(Data('2', ObjectChangeType.added, 'c'));
  items.add(Data('1', ObjectChangeType.modified, 'd'));
  items.add(Data('3', ObjectChangeType.added, 'e'));
  final DataEvent event = DataEvent(items);

  final Map<String, Data> state = Map<String, Data>();
  final Map<String, Data> newState = listenEventReducer<Data>(state, event);

  expect(newState, isNotNull);
  expect(newState.length, 3);
  expect(newState['0'], isNull);
  expect(newState['1'].metadata, 'd');
  expect(newState['2'].metadata, 'c');
  expect(newState['3'].metadata, 'e');
}

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    问题在于typedef 和函数类型_defaultUpdate 不完全匹配,并为编译器提供有关泛型类型的完整信息。

    初始化[UpdateItem&lt;T&gt; update = _defaultUpdate]时,编译器期望UpdateItem类型为T,但它不知道_defaultUpdate是否实际上返回T,其中T extends ListenEventItem_defaultUpdate可以返回任何类型S S extends ListenEventItem.

    比如说你有两个扩展ListenEventItem的类

    class A extends ListenEventItem {}
    

    class B extends ListenEventItem {}
    

    现在,当没有传递泛型参数时,_defaultUpdate 函数可以返回 AB。 当你尝试像这样初始化它时

    UpdateItem<A> update = _defaultUpdate
    

    编译器不知道_defaultUpdate 是否返回AB 使其在编译时失败。


    你可以解决这个问题,让 typedef 函数接受泛型参数,这样编译器就知道期待任何扩展 ListenEventItem 的类

    例子:

    typedef UpdateItem = T Function<T extends ListenEventItem>(T oldItem, T newItem);
    
    T _defaultUpdate<T extends ListenEventItem>(T oldItem, T newItem) => newItem;
    

    完整代码:

    import 'package:test/test.dart';
    
    enum ObjectChangeType { added, modified, removed }
    
    typedef UpdateItem = T Function<T extends ListenEventItem>(T oldItem, T newItem);
    
    T _defaultUpdate<T extends ListenEventItem>(T oldItem, T newItem) => newItem;
    
    abstract class ListenEventItem {
      String get id;
    
      ObjectChangeType get changeType;
    }
    
    abstract class ListenEvent<T extends ListenEventItem> {
      List<T> get data;
    
      Map<ObjectChangeType, List<T>> get typedData {
        return data.fold(
          <ObjectChangeType, List<T>>{},
          (Map<ObjectChangeType, List<T>> map, T element) {
            map[element.changeType] = (map[element.changeType] ?? <T>[])
              ..add(element);
    
            return map;
          },
        );
      }
    }
    
    Map<String, T> listenEventReducer<T extends ListenEventItem>(
        Map<String, T> state, ListenEvent<T> action,
        [UpdateItem update = _defaultUpdate]) {
      update ??= _defaultUpdate;
      final Map<String, T> b = Map<String, T>.from(state);
    
      for (ObjectChangeType type in ObjectChangeType.values) {
        final List<T> data = action.typedData[type];
        if (data == null || data.isEmpty) {
          continue;
        }
    
        if (type == ObjectChangeType.added) {
          for (T item in data) {
            b[item.id] = item;
          }
        } else if (type == ObjectChangeType.modified) {
          for (T item in data) {
            b[item.id] = update(b[item.id], item);
          }
        } else if (type == ObjectChangeType.removed) {
          final List<String> ids = data.map((T it) => it.id).toList();
          b.removeWhere((String id, _) => ids.contains(id));
        }
      }
    
      return b;
    }
    
    class Data extends ListenEventItem {
      Data(this.id, this.changeType, this.metadata);
    
      final String metadata;
    
      @override
      final String id;
    
      @override
      final ObjectChangeType changeType;
    }
    
    class DataEvent extends ListenEvent<Data> {
      DataEvent(this.data);
    
      @override
      final List<Data> data;
    }
    
    void main() {
      final List<Data> items = List<Data>();
      items.add(Data('0', ObjectChangeType.added, 'a'));
      items.add(Data('1', ObjectChangeType.added, 'b'));
      items.add(Data('0', ObjectChangeType.removed, 'a'));
      items.add(Data('2', ObjectChangeType.added, 'c'));
      items.add(Data('1', ObjectChangeType.modified, 'd'));
      items.add(Data('3', ObjectChangeType.added, 'e'));
      final DataEvent event = DataEvent(items);
    
      final Map<String, Data> state = Map<String, Data>();
      final Map<String, Data> newState = listenEventReducer<Data>(state, event);
    
      test('Test', () {
        expect(newState, isNotNull);
        expect(newState.length, 3);
        expect(newState['0'], isNull);
        expect(newState['1'].metadata, 'd');
        expect(newState['2'].metadata, 'c');
        expect(newState['3'].metadata, 'e');
      });
    }
    

    【讨论】:

    • Raj,你是最棒的。我印象深刻。 :D
    • 感谢@RazvanCristianLung!我只是回报你,希望你记得帮助我掌握 redux :P
    • 请注意,错误的解释有些不正确——正确的解释是默认参数值必须是常量,这意味着它们不能依赖于任何类型参数。
    【解决方案2】:

    实际上不需要将参数声明为[UpdateItem update = _defaultUpdate],声明update ??= _defaultUpdate; 确保update 无论如何都会获得所需的值。所以只使用[UpdateItem update]就解决了默认值不是常量的问题。

    updateT Function(T, T) 类型(其中T 是给定类型,在调用时由listenEventReducer 作为实际类型参数接收)与_defaultUpdateT Function&lt;T extends ListenEventItem&gt;(T , T) 类型不匹配(其中T 由函数类型本身声明)由“通用函数实例化”解决。这是一个简单的例子:

    X foo<X>(X x) => x;
    
    main() {
      int Function(int) f = foo;
      print(f(42));
      // But `f('Hello!')` is a type error.
    }
    

    重点是foo 是一个泛型函数,但它可以通过推理作为函数接收其实际类型参数,结果是int Function(int) 类型的函数(即非泛型函数)。它将接受int 并返回int,即使您动态调用它也会强制执行此规则(例如,(f as dynamic)('Hello!') 将抛出);所以生成的函数对象是一个正确的int Function(int),而不仅仅是foo,名称不同。

    如果你想明确地模拟同样的事情,那么你可以编写一个包装函数:

    X foo<X>(X x) => x;
    
    main() {
      int Function(int) f = (int x) => foo<int>(x);
      print(f(42));
      // But `f('Hello!')` is a type error.
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-10
      • 2022-01-15
      相关资源
      最近更新 更多