Подтвердить что ты не робот

Можно ли обозначить без монад: возможно?

У меня есть тип состояния с операторами >> и >>=, который почти является монадой. Предполагаемое использование - генерировать код для другого языка, а доступность do-notation будет очень уместной.

Тем не менее, нет четко определенной функции возврата, так как значения должны только создаваться вместе с побочными эффектами. Итак, если я подделываю функцию возврата, возвращаемая функция должна возвращать только ошибку, и она нарушает законы монады. (I.e. никогда не действует для использования функции возврата.)

Что я наблюдаю, так это то, что do-notation только сахарирует два оператора, операторы >> и >>=.

Есть ли способ сохранить что-то вроде обозначений для этих двух операторов или что-то такое же чистое, но без создания монады?

ПРИМЕЧАНИЕ. Ниже строки указаны детали, которые не нужны для вопроса в заголовке. Это контекст для того, почему я задал вопрос, ответы на которые мне интересны. Другие, которые просматривают эту тему, вероятно, предпочли бы игнорировать ее.


КОРРЕКЦИЯ: Я не создаю код на императивном языке, хотя это не имеет значения. Я генерирую код в Javascript.

CLARIFICATION (в ответ на bdonlan): My type is (ma), который несет состояние (это код, который будет сгенерирован с учетом различных параметров, аналогичный государственной монаде) и вернет значение (имена переменных в сгенерированный код с прикрепленным типом haskell). Таким образом, не должно быть способа вернуть значения, которые связаны с именами переменных, без генерации кода, который определяет имена переменных. * Это исключительно из-за моего дизайна, который может быть хуже других, о которых я не думал.

В ответ на ответы: Кажется, есть два вида ответов на это. Во-первых, действительно ли это возможно и, возможно, лучший способ использовать обозначение do. Второй вопрос о том, лучше ли использовать монаду и определить возврат (или даже имеет смысл не делать этого - возможно, в более поздний момент времени, я бы нашел, что требуется возврат).

Пример того, что я буду делать:

data JsState = JsState { code :: String , vidCount :: Int } 
-- vidCount is for generating unique variable names

newtype JsStmt = JsStmt ( JsState -> JsState )

newtype JsMonad a = JsMonad ( JsState -> ( a , JsState ) )

def :: (JsValue a) => a -> JsMonad a

-- Outputs "var " ++ toUniqueVarName vidCount ++ " = " toCode a ++ ";"

-- Returns JsMonad with the variable name as the value,
-- with a type attached, and the JsState vidCount is incremented by 1.


alertJsInt :: JsIntE -> JsMonad ()

-- Outputs something like "alert(" ++ toCode JsIntE ++ ");"

do
  x <- def $ JsIntL 2
  y <- def $ JsIntL 4
  alertJsInt (x + y)
4b9b3361

Ответ 1

Ну, структура, с которой вы работаете, уже существует; a соответствующий тип класса можно найти в Hackage. Я не рекомендую вставлять его в экземпляр Monad, хотя по причинам, которые дает bdonlan. Существование return довольно фундаментально, и вы, вероятно, столкнулись с трудностями во всем мире, пытаясь использовать стандартные функции для Monad s.

.... Однако! Тем не менее, если вам действительно нужна нотация do, рассмотрите эту цитату из руководства пользователя GHC:

"Do" нотация переводится с использованием любых функций ( → =), ( → ) и сбоев, в области (не версии Prelude).

... другими словами, вы правы в том, что использование обозначения do имеет смысл, потому что нигде оно фактически не использует return, когда desugared. Этот раздел посвящен расширению RebindableSyntax, что делает именно то, что он утверждает. Если вы не возражаете отказаться от нотации do для Monad, вы можете использовать это расширение, определить свои собственные функции и использовать нотацию do, которая вам нравится.

Ответ 2

Идиоматическим решением было бы использовать два монада Writer и State-Writer для накопления кода "String" и State для отслеживания счетчика переменных.

Вы можете объединить две монады в одну объединенную монаду, если вы не хотите использовать библиотеку трансформаторов монады:

-- There are better types to use rather than String...
type Output = String
type St  = Int

newtype JsMonad a = JSMonad { getJSMonad :: St -> (a, St, Output) }

[Поскольку вы используете стандартную комбинацию из двух стандартных монад, очевидное определение возврата и привязки не нарушит законы монады.]

Это довольно распространенный шаблон для создания встроенных DSL-систем, которые используют примечание пользователя, например, в библиотеке Andy Gill Dot для Hackage. Олег Киселев также имеет библиотеку XML в этом стиле (CSXML - не в Hackage).

Как вы не заботитесь о конечном ответе, обычно записывается функция "run", которая игнорирует ее:

runJS :: JSmonad a -> Output
runJS ma = post $ getJSMonad ma 0
  where
    post (_,_,w) = w

Следует отметить, что выходной код - это всего лишь "журнал" - вы не можете его проверять при его создании, поэтому это ограничивает некоторые действия, которые вы можете сделать.

Ответ 3

Было бы очень сложно использовать обозначение без return. Рассмотрим общую схему:

foo = do
    x <- bar
    y <- quux
    return $ x + y

Без возврата невозможно определить это. Напомним, что >>= имеет тип (>>=) :: Monad m => m a -> (a -> m b) - он должен возвращать значение внутри монады. Поэтому, даже если мы напишем это без обозначения, мы имеем ту же проблему:

foo = bar >>= \x -> quux >>= \y -> (???????) (x + y)

Так что не стоит использовать do-notation для этого. В частности, нечего останавливать реализацию компилятора от введения return с преобразованиями, соответствующими законам монады (которые предполагают наличие return).

Более того, даже определение → = будет сложно для вас. Как вы переведете его на свой императивный язык? Помните, что → = принимает произвольную функцию в своем правом аргументе; вы не можете проверить эту функцию, чтобы рассказать, что она может сделать, и, следовательно, не может перевести тело этой функции на императивный язык. Я предполагаю, что вы можете ограничить тип аргумента каким-то прослеживаемым типом данных и попытаться выяснить эффекты функции, но теперь это означает, что функция не может проверить свой аргумент. Короче говоря, он просто не будет работать очень хорошо.

Один альтернативный подход, который вы можете рассмотреть, - создать Monad, который представляет собой процесс создания императивной функции. Это может выглядеть, например, так:

emitAddAndPrint varA varB = do
    varTmp <- allocateTempVariable
    emitOp (Add varTmp varA varB)
-- if you want to be fancy, emitOp (varTmp :=: varA :+: varB) or something
    emitCall "print" [varTmp]

Ответ 4

Похоже, у вас есть arrow. В монаде вам нужно написать:

do
   x <- return $ f y
   if x then something else notSomething

Условие "if" оценивается как часть оценки "do", и результат является либо "что-то", либо "notSomething", но не одновременно. Однако для генерации кода вы, вероятно, хотите, чтобы "if" транслировался в ваш сгенерированный код, причем обе ветки оценивались для создания кода, который может сделать выбор во время выполнения.

Эквивалентный код для стрелки desugars для использования класса ArrowChoice, в котором у вас есть доступ как к условию, так и к обоим ветвям, что вам здесь нужно.

Ответ 5

Если вы можете сделать свой тип экземпляром Monad, даже с отсутствующим return, то будет работать нотация do. Я сделал это (например, BASIC), и это отличный способ получить обязательную нотацию для DSL.