Здравствуйте, коллеги!
Не так давно набрёл на большие ограничения вывода типов в 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);
}
}