Skip to content

q42jaap/TestTypeInference

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

TestTypeInference

I was trying to make a fluent API with generics and descriptors that extend each other. In this repo I've demonstrated 5 steps I took to create an API that supports fluent syntax. Program5 contains the ultimate attempt to do the fluent API with generic descriptors.

For a real example check out Nest's facet descriptors:

I tried to work out a way to get rid of all the "new" methods:

public new RangeFacetDescriptor<T, K> Global()
{
  this._IsGlobal = true; // _IsGlobal is internal so Nest's inspectors of the descriptor can access it
  return this;
}

The problem is that we can't put the Global() method in the BaseFacetDescriptor, because it will break the fluentness of the descriptor:

var rangeFacetDescriptor = new RangeFacetDescriptor<MyType, int>();
rangeFacetDescriptor
  .Global() // this method comes from the BaseFacetDescriptor, but needs to be newed in RangeFacetDescriptor
  .Ranges() // because we want to call e.g. Ranges() on it which belongs to RangeFacetDescriptor only

A way to solve this for Global() is to take the implementation to an extension method, the extension method could be made generic, the type param would be need to have a constraint on a non-generic base class. For BaseFacetDescriptor this was easy ( https://github.com/q42jaap/NEST/blob/descriptors-extensionmethods/src/Nest/DSL/Facets/BaseFacetDescriptor.cs)

public static class BaseFacetDescriptorExtensions
{
	  public static TDescriptor Global<TDescriptor>(this TDescriptor descriptor)
  		where TDescriptor : BaseFacetDescriptor
  {
    descriptor._IsGlobal = true;
    return descriptor;
  }
}

The problem arises with the FacetFilter() method because it actualy uses the type parameter from BaseFacetDescriptor

  public new DateHistogramFacetDescriptor<T> FacetFilter(
  Func<FilterDescriptor<T>, BaseFilter> facetFilter
)
{
  var filter = facetFilter(new FilterDescriptor<T>());
  this._FacetFilter = filter;
  return this;
}

The extension method would look like this:

  public static TDescriptor FacetFilter<TDescriptor, T>(this TDescriptor descriptor, Func<FilterDescriptor<T>, BaseFilter> facetFilter)
  		where TDescriptor : BaseFacetDescriptor
    where T : class
{
  var filter = facetFilter(new FilterDescriptor<T>());
  descriptor._FacetFilter = filter;
  return descriptor;
}

However, when calling the extension method like this:

var rangeFacetDescriptor = new RangeFacetDescriptor<MyType, int>();
rangeFacetDescriptor
  .FacetFilter(f => f.Terms(...))

you end up with the following compiler error:

The type arguments for method 'Nest.BaseFacetDescriptorExtensions.FacetFilter<TDescriptor,T>(TDescriptor, Func<FilterDescriptor<T>, BaseFilter>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Program5 from this repository demonstrates this. You can use the extension method, but it is very ugly:

 .Favorite<ExperiencedZooKeeper<Reptile>, Reptile>(...)

which is definitely not fluent at all.

The problem is the declaration of FacetFilter<TDescriptor, T>, because the T parameter has to be a part of the signature of the method. There is no way to relate the T to the TDescriptor in a type inference way of speaking :( As a human it seems evident, but the compiler is not a human...

About

A small test repo to test type inference in C#

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages