Упёрся в ограничения вывода типов или что-то упустил?
От: KeyKeeper Россия  
Дата: 29.11.11 21:34
Оценка:
Здравствуйте, коллеги!

Не так давно набрёл на большие ограничения вывода типов в generic'ах в C# (если я ничего не путаю, компилятор способен выводить типы только в generic-методах, но не классах/интерфейсах). После этого сразу вспомнил о Nemerle, где дела с выводом типов обстоят намного лучше. Стал разбираться, но всё-равно не смог решить свою задачу (либо тоже ограничение языка меня держит, либо незнание).

Формулировка задачи.
Дано:
Множество generic классов-реализаций абстрактного типа данных "дерево" (напр., BinarySearchTree[TKey,TValue], RedBlackTree[TKey,TValue] и проч.) Все эти классы реализуют интерфейс ITree[TKey,TValue]. Предположим, что работать со всеми этими типами неудобно.

Требуется: Реализовать класс-обёртку над любым из "неудобных" деревьев, реализующих ITree[TKey,TValue]. Назову этот тип TreeWrapper. Объекты класса TreeWrapper планируется создавать через фабричный метод, объявленный в самом же TreeWrapper. Класс TreeWrapper должен предоставить удобный интерфейс для работы с произвольным деревом (конкретный тип дерева указывается при создании TreeWrapper'a). Например, он должен избавить от необходимости явно указывать ключ, соответствующий значению при вставке в дерево. Поскольку TreeWrapper всё-таки должен будет передавать настоящему дереву какой-то ключ, TreeWrapper будет вычислять ключ, используя делегат, который ему специально передали при вызове фабричного метода.

Решение и проблема:
В принципе, основа решения мной получена. Проблема заключается в том, что я не могу избавиться от уродского дублирования типов ключей и значений "оборачиваемых" деревьев:
def wrappedBST = TreeWrapper.Create.[BinarySearchTree[string, int], string, int](keyGenerator);


По идее, компилиятору достаточно информации, чтобы уметь работать с таким вызовом или аналогичным (если я не прав, поправьте):
// да, я знаю, что синтаксически неверны оба варианта...
def wrappedBST = TreeWrapper.Create.[BinarySearchTree[string, int]](keyGenerator);
// или
def wrappedBST = TreeWrapper.Create.[BinarySearchTree, string, int](keyGenerator);


Иными словами, мой первый вопрос: поддерживает ли Nemerle "частичный вывод типов" (не знаю как правильно назвать)? Или же Nemerle как и C# эксплуатирует подход "всё или ничего"?

Второй вопрос: можно ли решить мою задачу каким-то изящным способом, не применяя макросов? Можно ли решить хотя бы с макросами (я пока с ними не знакомился, но что-то подсказывает, что они должны спасти)?

Ниже предаставлена моя попытка решения и ссылка на gist, если кому-то удобнее смотреть в нём. Заранее спасибо за любую помощь!
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Console;
using System.Linq;

interface ITree[TKey, TValue] { Add(_ : TKey, _ : TValue) : void; }

class RedBlackTree[TKey, TValue] : ITree[TKey, TValue]
{
  public Add(_ : TKey, _ : TValue) : void { }
}

class BinarySearchTree[TKey, TValue] : ITree[TKey, TValue]
{
  public Add(_ : TKey, _ : TValue) : void { }
}
    
class TreeWrapper[TKey, TValue]
{
  public static Create [TTree, TK, TV] (keyGenerator : TV -> TK) : TreeWrapper[TK, TV]
        where TTree : ITree[TK, TV], new()
        // where TK : TKey
        // where TV : TValue
  {
    def tree = TTree();
    TreeWrapper(keyGenerator, tree);
  }
    
  private this(keyGenerator : TValue -> TKey, tree : ITree[TKey, TValue])
  {
    // Сохранение неудобного в использовании объекта-реализации  tree,
    // чтобы затем работать с ним через удобный класс-обёртку TreeWrapper.
    this.tree = tree;
    this.keyGenerator = keyGenerator;
  }
    
  keyGenerator : TValue -> TKey;
  tree : ITree[TKey, TValue];
    
  public Add(value : TValue) : void
  {
    def key = keyGenerator(value);
    tree.Add(key, value);
  }
}

module Program
{
  Main() : void
  {
    def keyGenerator = @int : int => @int.ToString();
        
    // Ниже я пытаюсь выразить следующее: "Фабричный метод, создай мне обёртку над BinarySearchTree, у которого
    // ключи типа string, и значения типа int. Аргумент правильного типа также предоставляю."
    def wrappedBST = TreeWrapper.Create.[BinarySearchTree[string, int], string, int](keyGenerator);
        
    // А хотелось бы не дублировать типы int и string. Вместо этого, например, писать так:
    //def wrappedBST = TreeWrapper.Create.[BinarySearchTree[string, int]](keyGenerator);
    // или так
    //def wrappedBST = TreeWrapper.Create.[BinarySearchTree[?,?]](keyGenerator);
    // или так
    //def wrappedBST = TreeWrapper.Create.[BinarySearchTree[,]](keyGenerator);
    // или так
    //def wrappedBST = TreeWrapper.Create.[BinarySearchTree](keyGenerator);
        
    wrappedBST.Add(10);
  }
}
nemerle type inference generics вывод типов
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.