none_or 1.0.1 copy "none_or: ^1.0.1" to clipboard
none_or: ^1.0.1 copied to clipboard

Provides a new object None<T>() and a typedef for NoneOr<T>() to differentiate between implicit and explicit null values using dart's only valid Union Type.

Provides a new class (None<T>) that masquarades as a Future<T> to enable piggybacking on the only Union Type available in dart: FutureOr<T>. You can then differentiate between an explicit null value and a default None<T> value.

Unfortunately, the dart language lacks true Union Types and does not allow for extending the capabilities of FutureOr<T> so this package deploys some workarounds to use the NoneOr<T>? typedef as a standin paramter for a value that may be T, null, or None (which is a new class that implements Future but is in no way indended to actually behave like a future)


Big thank you to our sponsor Scrapp Inc for supporting the development of this package!


Features #

This package introduces a new class None<T> which is a valid implementor of Future<T> enabling it to be used in place of an argument or variable of type FutureOr<T>. Included in the None type is the fallback static method that can return T? taking from the primary field when primary is of type T? or is null. However, it can return the optional fallback parameter if primary is of type None<T> or Future<T>.


Getting started #

Add the package to your pubspec.yaml:

none_or: latest

Import the package with:

import 'package:none_or/none_or.dart';

See below for usage examples.


Usage #

The expected utility of this package is in allowing for differentiation between explitly null values and values that are simply omitted. The below example demonstrates how NoneOr<T> could be used to level-up a copyWith function for a class with nullable fields:

import 'package:none_or/none_or.dart';
import 'package:flutter/foundation.dart';

// ...

/// A simple example immutable class with a copyWith function that uses
/// NoneOr<T> to ignore implicit no-change values given to a copyWith, while
/// respecting explicitly provided values including `null`
@immutable
class _NoneOrExampleClass {
  final bool nonNullableField;
  final bool? nullableField;
  // ...

  const _NoneOrExampleClass({
    required this.nonNullableField,
    this.nullableField,
    // ...
  });

  @override
  String toString() => 'Example($nonNullableField, $nullableField)';

  /// Creates a copy of [this] with the given fields changed
  _NoneOrExampleClass copyWith({
    bool? nonNullableField,
    NoneOr<bool>? nullableField = const None(),
    // ...
  }) =>
      _NoneOrExampleClass(
        nonNullableField: nonNullableField ?? this.nonNullableField,
        nullableField: None.fallback(nullableField, this.nullableField),
        // ...
      );
  
  /// Creates a copy of [this] with the given fields changed
  _NoneOrExampleClass copyWithAlt({
    // For consistency's sake, you could make every field NoneOr<T> even if
    //  the field is non-nullable. That way it would read the same from a 
    //  logical perspective.
    NoneOr<bool> nonNullableField = const None(),
    NoneOr<bool>? nullableField = const None(),
    // ...
  }) =>
      _NoneOrExampleClass(
        nonNullableField: None.fallback(nonNullableField, this.nonNullableField),
        nullableField: None.fallback(nullableField, this.nullableField),
        // ...
      );

  _NoneOrExampleClass copyWithAlt2({
    NoneOr<bool> nonNullableField = const None(),
    // Note that NoneOr<bool?> is effectively the same as NoneOr<bool>?
    //  or even NoneOr<bool?>? - use whatever syntax most appeals to you.
    // In the future, we may introduce a custom lint rule to dictate where
    //  we can put the "?" character
    NoneOr<bool?>? nullableField = const None(),
    // ...
  }) =>
      _NoneOrExampleClass(
        nonNullableField: None.fallback(nonNullableField, this.nonNullableField),
        nullableField: None.fallback(nullableField, this.nullableField),
        // ...
      );
}

// ... 

void _exampleUsage() {
	// baseline
	_NoneOrExampleClass e0 = _NoneOrExampleClass(
		nonNullableField: true,
		nullableField: true,
	); // Example(true, true)

	// copies using copyWith:

	// simple example with non-nullable underlying fields:

	// 1. If field is ommitted, null is assumed, and existing value is kept (true)
	_NoneOrExampleClass s1 = e0.copyWith(); // Example(true, true)
	// 2. If field is marked null, existing value is kept (true)
	_NoneOrExampleClass s2 = e0.copyWith(nonNullableField: null); // Example(true, true)
	// 3. If field is given a value, value given overwrites existing (false)
	_NoneOrExampleClass s3 = e0.copyWith(nonNullableField: false); // Example(false, true)

	// simple example with nullable underlying fields:
	
	// 1. If field is ommitted, None() is assumed, and existing value is kept (true)
	_NoneOrExampleClass s1 = e0.copyWith(); // Example(true, true)
	// 2. If field is marked null, existing value is overwritten with null (null)
	_NoneOrExampleClass s2 = e0.copyWith(nonNullableField: null); // Example(true, null)
	// 3. If field is given a value, value given overwrites existing (false)
	_NoneOrExampleClass s3 = e0.copyWith(nonNullableField: false); // Example(true, false)
}

Performance #

Early testing suggests that this approach is less performant than alternate approaches such as using a second boolean variable to set a field. Run on your own system for comparison, but the rudimentary tests suggest that due to the overhead of calling additional functions and comparing types, the approach of using the second variable performs around ~100% faster than an approach of manually testing for type in a ternary operator.

The methodology used to test was to run three versions of copyWith over 10,000,000 runs. In fairness, that is an unlikely impediment for casual use copying a few values at a time, but if your usecase requires millions of copies over a long time horizon, you may wish to consider the runtime implications of this package's approach: this may save dev time and provide convenience to developers at the expense of runtime optimizations.

See none_or_test.dart for the code


Additional information #

Right now there is no linter rule to differentiate the different ways of using NoneOr<T> in your code. In the future, that may change. PRs are always welcome.


Deprecation #

It is a sincere hope that at some future point this package will be rendered obselete by future updates to the dart language. In the meantime, this is meant as a stopgap to provide the necessary functionality into the dart language in a way that aims to benefit from the type safety dart provides, while enabling unintended behavior.

It is also possible that future updates to the dart language will further block the workarounds this package uses in an attempt to crack down on this workaround. Use at your own risk.

1
likes
160
pub points
0%
popularity

Publisher

verified publishertre.contact

Provides a new object None<T>() and a typedef for NoneOr<T>() to differentiate between implicit and explicit null values using dart's only valid Union Type.

Repository

Documentation

API reference

License

BSD-3-Clause (LICENSE)

More

Packages that depend on none_or