Могут ли ML-функторы полностью кодироваться в .NET(С#/F #)?
Могут ли ML-функторы быть практически выражены с помощью интерфейсов и дженериков .NET? Есть ли пример использования расширенного ML-функтора, который бросает вызов таким кодировкам?
Резюме ответов:
В общем случае ответ НЕТ. Модули ML предоставляют функции (такие как совместное использование спецификаций через сигнатуры [1]), которые непосредственно не сопоставляются с понятиями .NET.
Однако для некоторых случаев использования идиомы ML могут быть переведены. Эти случаи включают не только основной функтор Set
[2], но также функторное кодирование монад [3] и еще более расширенное использование Haskell, например, наконец, бездекорные интерпретаторы [4, 5].
Практические кодировки требуют компромиссов, таких как полубезопасные понижения. Ваш пробег будет настороже.
Блоги и код:
Ответы
Ответ 1
Одной из ключевых особенностей модулей ML является разделение спецификаций. В .NET нет механизма, который мог бы ими подражать - требуемый механизм просто слишком отличается.
Вы можете попытаться сделать это, превратив общие типы в параметры, но это не может точно эмулировать способность определять подпись, а затем применять к ней совместное использование, возможно, несколькими способами.
По моему мнению,.NET выиграет от чего-то, у которого был такой вид техники, - тогда он приблизился бы к подлинной поддержке разнообразия современных языков. Будем надеяться, что в последние годы в модульных системах, таких как MixML, появятся новые достижения, которые, на мой взгляд, являются будущим модульных систем. http://www.mpi-sws.org/~rossberg/mixml/
Ответ 2
HigherLogics - это мой блог, и я потратил много времени на изучение этого вопроса. Ограничение - это, действительно, абстракция над конструкторами типов, например, "generics over generics". Кажется лучшим, что вы можете сделать, чтобы имитировать модули ML, и функторам требуется хотя бы одно (полубезопасное) литье.
В основном это сводится к определению абстрактного типа и интерфейсу, который соответствует сигнатуре модуля, который работает с этим типом. Абстрактный тип и интерфейс разделяют параметр типа B, который я называю "брендом"; бренд - это просто подтип, который реализует интерфейс модуля. Бренд гарантирует, что передаваемый тип является подходящим подтипом, ожидаемым модулем.
// signature
abstract class Exp<T, B> where B : ISymantics<B> { }
interface ISymantics<B> where B : ISymantics<B>
{
Exp<int, B> Int(int i);
Exp<int, B> Add(Exp<int, B> left, Exp<int, B> right);
}
// implementation
sealed class InterpreterExp<T> : Exp<T, Interpreter>
{
internal T value;
}
sealed class Interpreter : ISymantics<Interpreter>
{
Exp<int, Interpreter> Int(int i) { return new InterpreterExp<int> { value = i }; }
Exp<int, Interpreter> Add(Exp<int, Interpreter> left, Exp<int, Interpreter> right)
{
var l = left as InterpreterExp<int>; //semi-safe cast
var r = right as InterpreterExp<int>;//semi-safe cast
return new InterpreterExp<int> { value = l.value + r.value; }; }
}
}
Как вы можете видеть, литье в основном безопасно, так как система типов гарантирует, что бренд типа выражения соответствует бренду интерпретатора. Единственный способ испортить это, если клиент создает свой собственный класс Exp и указывает бренд Interpreter. Существует более безопасное кодирование, которое позволяет избежать этой проблемы, но оно слишком громоздко для обычного программирования.
Я позже использовал эту кодировку и перевел примеры из одной из статей Олега, написанной в MetaOCaml, для использования С# и Linq. Интерпретатор может прозрачно запускать программы, написанные с использованием этой встроенной языковой серверной части в ASP.NET или на стороне клиента как JavaScript.
Эта абстракция над интерпретаторами является особенностью окончательной бездекорной кодировки Олега. Ссылки на его статью приводятся в сообщении в блоге.
Интерфейсы являются первоклассными в .NET, и поскольку мы используем интерфейсы для кодирования подписей модулей, модули и подписи модулей также являются первоклассными в этой кодировке. Таким образом, функторы просто используют интерфейс непосредственно вместо сигнатур модуля, т.е. они будут принимать экземпляр ISymantics <B> и делегировать ему любые вызовы.
Ответ 3
Я не очень хорошо знаю ML-функторы, чтобы действительно ответить на ваш вопрос. Но я скажу, что один ограничивающий фактор .Net, который я всегда нахожу с монадическим программированием, - это неспособность абстрагироваться над "М" в смысле "forall M. какое-то выражение типа с M <T> " (например, где M является типом constructor (тип, который принимает один или несколько общих аргументов)). Поэтому, если это то, что вам иногда нужно/нужно использовать с функторами, то я уверен, что нет хорошего способа выразить это на .Net.
Ответ 4
Теперь я разместил подробное описание моего перевода для модулей ML, подписей и функторов на эквивалентную С# -кодировку. Надеюсь, кто-то сочтет это полезным.
Ответ 5
Комментарий Брайана. Вот код OCaml, который использует функторы для предоставления (строгой) реализации Haskell sequence :: (Monad m) => [m a] -> m [a]
, параметризованной над рассматриваемой монадой:
module type Monad =
sig
type 'a t (*'*)
val map : ('a -> 'b) -> ('a t -> 'b t)
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
end
module type MonadUtils =
sig
type 'a t (*'*)
val sequence : ('a t) list -> ('a list) t
end
module MakeMonad (M : Monad) : MonadUtils =
struct
type 'a t = 'a M.t
let rec sequence = function
| [] ->
M.return []
| x :: xs ->
let f x =
M.map (fun xs -> x :: xs) (sequence xs)
in
M.bind x f
end
Это выглядит сложным для выражения в .NET.
UPDATE
Используя метод naasking
, я смог кодировать функцию многократного использования sequence
в F # в основном безопасном для типа (использует downcasts).
http://gist.github.com/192353