#haskell #monads #do-notation
#хаскелл #монады #условные обозначения
Вопрос:
Когда я изучаю Haskell, я понимаю, что do
нотация — это просто синтаксический сахар:
a = do x <- [3..4]
[1..2]
return (x, 42)
Переводится в
a = [3..4] >>= (x -> [1..2] >>= (_ -> return (x, 42)))
Я понимаю, что, вероятно, буду использовать обозначение do, но я хотел бы понять, что происходит при переводе. Итак, чисто по педагогическим соображениям, есть ли способ для ghc / ghci предоставить мне соответствующие инструкции привязки для довольно сложной монады, написанной в do-нотации?
Редактировать. Оказывается, lambdabot на #haskell может это сделать:
<Guest61347> @undo do x <- [3..4] ; [1..2] ; return (x, 42)
<lambdabot> [3 .. 4] >>= x -> [1 .. 2] >> return (x, 42)
Вот исходный код плагина отмены.
Комментарии:
1. на самом деле
m >>= (_ -> k)
этоm >> k
2. У Lambdabot есть плагин «отменить», который удаляет обозначения
3. Я просто учусь и чувствую, что это ключ к реальному пониманию деталей конкретного экземпляра — все они написаны в терминах >>=
Ответ №1:
Вы можете запросить выходные данные у GHC desugarer , однако это также приведет к удалению множества других синтаксисов.
Сначала мы поместим ваш код в модуль Foo.hs
:
module Foo where
a = do x <- [3..4]
[1..2]
return (x, 42)
Далее мы попросим GHC скомпилировать его и вывести результат после этапа десугаринга:
$ ghc -c Foo.hs -ddump-ds
Вывод может выглядеть довольно беспорядочно, потому что это вариант Haskell под названием Core, который используется в качестве промежуточного языка GHC. Однако, это не слишком сложно читать, как только вы к этому привыкнете. В середине некоторых других определений мы находим ваше:
Foo.a :: [(GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)]
LclIdX
[]
Foo.a =
>>=_agg
@ GHC.Integer.Type.Integer
@ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
(enumFromTo_ag7
(GHC.Integer.smallInteger 3) (GHC.Integer.smallInteger 4))
( (x_adf :: GHC.Integer.Type.Integer) ->
>>_agn
@ GHC.Integer.Type.Integer
@ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
(enumFromTo_ags
(GHC.Integer.smallInteger 1) (GHC.Integer.smallInteger 2))
(return_aki
@ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
(x_adf, GHC.Integer.smallInteger 42)))
Ядро не слишком красиво, но возможность его чтения очень полезна при работе с GHC, поскольку вы можете прочитать дамп после более поздних этапов, чтобы увидеть, как GHC оптимизирует ваш код.
Если мы удалим _xyz
суффиксы, добавленные переименователем, а также приложения типа @ Xyz
и вызовы GHC.Integer.smallInteger
, и снова сделаем операторы инфиксными, у вас останется что-то вроде этого:
Foo.a :: [(GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)]
Foo.a = enumFromTo 3 4 >>= x -> enumFromTo 1 2 >> return (x, 42)
Комментарии:
1. Что вызывает вопрос… есть ли что-нибудь, что автоматически удалит суффиксы _xyz, приложения типа, вызовы GHC. Integer.SmallInteger и снова сделать операторы инфиксными?
2. @missingno: Ничего, о чем я знаю. Тем не менее, существуют библиотеки, доступные для работы с Haskell или Core в Hackage, поэтому не должно быть слишком сложно взломать что-то вместе, если вы хотите этого достаточно сильно.
3. Проблема в том, что к тому времени, когда вы напишете код для этого, он вам больше не понадобится.
4. Просто для удовольствия, быстрый и грязный разделитель условных обозначений для Haskell 98 .
5. @отсутствует флаг для подавления достаточности,
ghc -ddump-ds -dsuppress-uniques Foo.hs
который удалит их из выходных данных. Начиная с ghc-7.2, также есть-ddump-to-file
флаг, который заставит ghc сбрасывать выходные данные в, в данном случае Foo.dump-ds . Очень полезно, если вам нужно больше одного дампа, например,ghc -ddump-ds -ddump-simpl -ddump-to-file Foo
создаст Foo.dump-ds и Foo.dump.simpl (кстати.,-ddump-simpl
также обеспечивает разделяющую запись, если вы компилируете без оптимизации).