Ответ 1
Какая функция отсутствует в системе типа Java? Как эти другие языки объявляют тип Monad?
Хороший вопрос!
Эрик Липперт относится к этому как к более высоким типам, но я не могу обойти их вокруг.
Ты не одинок. Но они на самом деле не такие сумасшедшие, как они звучат.
Позвольте ответить на оба вопроса, взглянув на то, как Haskell объявляет "тип" монады - вы увидите, почему цитаты за минуту. Я несколько упростил его; стандартный шаблон монады также имеет пару других операций в Haskell:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
Мальчик, который выглядит одновременно невероятно простым и полностью непрозрачным, не так ли?
Здесь, позвольте мне упростить это немного больше. Haskell позволяет вам объявить свой собственный инфиксный оператор для привязки, но мы просто назовем его bind:
class Monad m where
bind :: m a -> (a -> m b) -> m b
return :: a -> m a
Хорошо, теперь, по крайней мере, мы видим, что есть две операции монады. Что это значит?
Первое, что нужно сделать, как вы заметили, - это "более высокие типы". (Как указывает Брайан, я несколько упростил этот жаргон в своем первоначальном ответе. Также довольно забавно, что ваш вопрос привлек внимание Брайана!)
В Java "класс" является своего рода "типом", и класс может быть общим. Итак, на Java у нас есть int
и IFrob
и List<IBar>
, и они все типы.
С этого момента вы выбрасываете любую интуицию, которую вы имеете о Жирафе, являющемся классом, который является подклассом Animal и т.д.; нам это не понадобится. Подумайте о мире без наследования; он больше не войдет в эту дискуссию.
Что такое классы в Java? Наилучшим способом думать о классе является то, что это имя для набора значений, которые имеют что-то общее, так что любое из этих значений может использоваться, когда требуется экземпляр класса. У вас есть класс Point
, скажем, и если у вас есть переменная типа Point
, вы можете присвоить ей любой экземпляр Point
. Класс Point
в некотором смысле является просто способом описания множества всех экземпляров Point
. Классы - это нечто большее, чем экземпляры.
В Haskell существуют также общие и не общие типы. Класс в Haskell не является не. В Java класс описывает набор значений; в любое время, когда вам нужен экземпляр класса, вы можете использовать значение этого типа. В Haskell класс описывает набор типов. Это ключевая особенность, которая отсутствует в системе типа Java. В Haskell класс выше, чем тип, который выше экземпляра. Java имеет только два уровня иерархии; У Хаскелла три. В Haskell вы можете выразить идею "в любое время, когда мне нужен тип, который имеет определенные операции, я могу использовать член этого класса".
(ASIDE: Я хочу указать здесь, что я делаю немного упрощения. Рассмотрим в Java, например, List<int>
и List<String>
. Это два "типа", но Java считает их "одним", класс ", поэтому в некотором смысле у Java также есть классы, которые являются" более высокими ", чем типы. Но опять же, вы можете сказать то же самое в Haskell, что list x
и list y
являются типами, а list
- вещь это выше, чем тип, это вещь, которая может создать тип. Поэтому на самом деле было бы более точно сказать, что Java имеет три уровня, а у Haskell четыре. Точка остается, хотя: у Haskell есть концепция описания операций доступный по типу, который просто более мощный, чем Java. Мы рассмотрим это более подробно ниже.)
Итак, как это отличается от интерфейса? Это похоже на интерфейсы в Java - вам нужен тип, который имеет определенные операции, вы определяете интерфейс, описывающий эти операции. Мы увидим, чего не хватает на интерфейсах Java.
Теперь мы можем начать понимать этот Haskell:
class Monad m where
Итак, что такое Monad
? Это класс. Что такое класс? Это набор типов, которые имеют что-то общее, так что всякий раз, когда вам нужен тип, который имеет определенные операции, вы можете использовать тип Monad
.
Предположим, что у нас есть тип, являющийся членом этого класса; назовите его m
. Какие операции должны выполняться для этого типа, чтобы этот тип был членом класса Monad
?
bind :: m a -> (a -> m b) -> m b
return :: a -> m a
Название операции происходит слева от ::
, а подпись - справа. Поэтому, чтобы быть Monad
, тип m
должен иметь две операции: bind
и return
. Каковы подписи этих операций? Сначала рассмотрим return
.
a -> m a
m a
- это Haskell для того, что в Java было бы M<A>
. То есть это означает, что m
является общим типом, a
является типом, m a
является m
параметризованным с помощью a
.
x -> y
в Haskell является синтаксисом для "функции, которая принимает тип x
и возвращает тип y
". Это Function<X, Y>
.
Объедините его, и мы имеем return
- это функция, которая принимает аргумент типа a
и возвращает значение типа m a
. Или в Java
static <A> M<A> Return(A a);
bind
немного сложнее. Я думаю, что OP хорошо понимает эту подпись, но для читателей, которые не знакомы с кратким синтаксисом Haskell, позвольте мне немного расшириться.
В Haskell функции принимают только один аргумент. Если вам нужна функция из двух аргументов, вы создаете функцию, которая принимает один аргумент и возвращает другую функцию одного аргумента. Поэтому, если у вас есть
a -> b -> c
Тогда что у тебя? Функция, которая принимает a
и возвращает a b -> c
. Предположим, вы хотели сделать функцию, которая взяла два числа и вернула их сумму. Вы должны сделать функцию, которая принимает первое число, и возвращает функцию, которая принимает второе число и добавляет его к первому числу.
В Java вы скажете
static <A, B, C> Function<B, C> F(A a)
Итак, если вы хотели C, и у вас были и A, и B, вы могли бы сказать
F(a)(b)
Имеют смысл?
Хорошо, поэтому
bind :: m a -> (a -> m b) -> m b
- фактически функция, которая принимает две вещи: a m a
и a a -> m b
и возвращает m b
. Или, на Java, это прямо:
static <A, B> Function<Function<A, M<B>>, M<B>> Bind(M<A>)
Или, более идиоматично в Java:
static <A, B> M<B> Bind(M<A>, Function<A, M<B>>)
Итак, теперь вы видите, почему Java не может напрямую представлять тип монады. У него нет возможности сказать "у меня есть класс типов, которые имеют общий характер".
Теперь вы можете сделать все монадические типы, которые вы хотите на Java. То, что вы не можете сделать, это создать интерфейс, представляющий идею "этот тип - тип монады". То, что вам нужно сделать, это что-то вроде:
typeinterface Monad<M>
{
static <A> M<A> Return(A a);
static <A, B> M<B> Bind(M<A> m, Function<A, M<B>> f);
}
Посмотрите, как интерфейс типа говорит о самом родовом типе? Монадическим типом является любой тип m
, который является общим с одним параметром типа и имеет эти два статических метода. Но вы не можете этого сделать в системах типа Java или С#. bind
, конечно, может быть методом экземпляра, который принимает M<A>
как this
. Но нет никакого способа сделать return
ничего, кроме статического. Java не дает вам возможности (1) параметризовать интерфейс неконструированным общим типом и (2) не позволяет указать, что статические члены являются частью контракта интерфейса.
Так как существуют языки, которые работают с монадами, эти языки должны каким-то образом объявить тип Monad.
Хорошо, что вы так думаете, но на самом деле нет. Во-первых, конечно, любой язык с достаточной системой типов может определять монадические типы; вы можете определить все монадические типы, которые вы хотите на С# или Java, вы просто не можете сказать, что у всех их есть в системе типов. Вы не можете создать общий класс, который может быть параметризирован только монадическими типами.
Во-вторых, вы можете встроить шаблон монады на языке другими способами. С# не может сказать, что "этот тип соответствует шаблону монады", но С# имеет понимание запросов (LINQ), встроенное в язык. Понимание запросов работает над любым монадическим типом! Это просто, что операция связывания должна называться SelectMany
, что немного странно. Но если вы посмотрите на подпись SelectMany
, вы увидите, что это просто bind
:
static IEnumerable<R> SelectMany<S, R>(
IEnumerable<S> source,
Func<S, IEnumerable<R>> selector)
Это реализация SelectMany
для последовательности monad, IEnumerable<T>
, но в С#, если вы пишете
from x in a from y in b select z
тогда тип a
может иметь любой монадический тип, а не только IEnumerable<T>
. Требуется, чтобы a
был M<A>
, что b
является M<B>
и что существует подходящий SelectMany
, который следует за шаблоном монады. Так что другой способ вложения "распознавателя монады" в язык, не представляя его непосредственно в системе типов.
(предыдущий абзац на самом деле является ложью упрощения, шаблон привязки, используемый этим запросом, несколько отличается от стандартного монадического связывания по соображениям производительности. Концептуально это признает шаблон монады, в действительности детали немного отличаются. здесь http://ericlippert.com/2013/04/02/monads-part-twelve/, если вам интересно.)
Еще несколько мелких точек:
Мне не удалось найти обычно используемое имя для третьей операции, поэтому я просто назову это функцией unbox.
Хороший выбор; его обычно называют операцией "extract". Монаде не нужно открывать операцию извлечения, но, как бы то ни было, bind
должен получить a
из M<A>
, чтобы вызвать Function<A, M<B>>
на нем, поэтому логически какой-то извлечения работа обычно бывает.
Комонад - обратная монада, в некотором смысле - требует операции extract
; extract
по существу return
назад. Для comonad также требуется операция extend
, которая является видом bind
, повернутой назад. Он имеет подпись static M<B> Extend(M<A> m, Func<M<A>, B> f)