Могут ли 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