169 похожих чатов

Расскажу я вам сейчас одну офигенную историю, что со мной

произошла.

Итак, коллега решал для себя простенькую задачу по реализации интерпретатора бреинфак-подобного языка на js и я решил, что чего бы мне тоже не написать, на хаскеле естественно.

Сразу была написана фримонада для AST и интерпретатор. Команды всего четыре, инкремент значения в памяти, декремент, установка в нуль и вывод. Поэтому для интерпретатора я заюзал StateT для памяти и WriterT для вывода. Думаю, код понятен, если работали с free (если нет, пишите, я все с радостью объясню в любое время суток).

runBrainscrambler :: Brainscrambler a -> String
runBrainscrambler
= flip execWriterT ""
. flip evalStateT 0
. foldFree go
where
go (Increment next) = modify succ >> pure next
go (Decrement next) = modify pred >> pure next
go (PushZero next) = put 0 >> pure next
go (Output next) = get >>= tell . show >> pure next

ВАЖНО ЗАМЕТИТЬ: код полностью рабочий, он не просто компилируется, он выдаст правильный результата на любых примерах.

Но тут есть несколько ошибок, которые я не заметил, так как не написал тайпинги, мол хаскель сам выведет. Кто их уже заметил, молодец, остальным тоже советую посидеть и поискать.

В проекте было подключено OverloadedString-расширение, так что любой строковой литерал полиморфен и компилятор будет кидать ворнинг о том, что "используется дефолтный тип, так как тип не указан явно", если тип не указать явно. Собственно в коде выше "" не аннотирован явно, но тип четко выводится из результата функции (String). Ворнинг тем не менее был:

• Defaulting the following constraint to type ‘[Char]’
IsString b0 arising from the literal ‘""’
• In the second argument of ‘flip’, namely ‘""’
In the first argument of ‘(.)’, namely ‘flip execWriterT ""’
In the expression:
flip execWriterT "" . flip evalStateT (0 :: Int) . foldFree go
|
16 | = flip execWriterT ""
| ^^

Для меня это стала загадкой, с просьбой решить ее я обратился в @haskellru, но там никто не смог помочь (говорили про то, что ворнинг из-за того, что нет аннотации, но не увидили, что тип четко известен, полагаю никто, как я, в чужой код не вчитывается).

И вот я решил добавить тайпинги и весь код сломался:

go :: BrainscramblerF a -> StateT Int (Writer String) a

И вот тут-то я наконец понял, в чем дело.
1. У меня Writer, а не WriterT, поэтому нужно execWriter, а не execWriterT
2. (exec|eval|run)WriterT? не принимают начального "состояния", так как они работают с моноидом и берут в качестве начального вывода mempty.

Эти две ошибки вместе дают ОЧЕНЬ интересный результат. Раз execWriterT не принимает начального состояния, то почему выше код работал? Что именно flip флипал? И тут до меня дошло озарение я в спешке побежал проверять:

:t flip execWriterT
flip execWriterT :: b -> WriterT c ((->) b) a -> c

Именно, из-за flip хаскель вывел инстанс монады для (->) b, то есть Reader. И использовал этот (->) b как монаду для дыры монадного трансформера WriterT. Тот "" был не начальным состоянием WriterT, а энвароментом Reader, который в коде нигде явно не используется (нет ask), поэтому и тип вывести не получается, поэтому и был ворнинг.

Это эпичнейшая ошибка в моей жизни, скажу честно. Итоговый правильный код выглядит так:

runBrainscrambler :: Brainscrambler a -> String
runBrainscrambler
= execWriter
. flip evalStateT (0 :: Int)
. foldFree go
where
go :: BrainscramblerF a -> StateT Int (Writer String) a
go (Increment next) = modify succ >> pure next
go (Decrement next) = modify pred >> pure next
go (PushZero next) = put 0 >> pure next
go (Output next) = get >>= tell . show >> pure next

Вот вам и статически типизированный хаскель с выводом типов - выведет даже неверный код в верный, как тут хаскель не любить-то.

1 ответов

13 просмотров

Пиши на ruHaskell мини-статью

Похожие вопросы

Обсуждают сегодня

а через ESC-код ?
Alexey Kulakov
29
30500 за редактор? )
Владимир
47
Чёт не понял, я ж правильной функцией воспользовался чтобы вывести отладочную информацию? но что-то она не ловится
notme
18
У меня есть функция где происходит это: write_bit(buffer, 1); write_bit(buffer, 0); write_bit(buffer, 1); write_bit(buffer, 1); write_bit(buffer, 1); w...
~
13
Недавно Google Project Zero нашёл багу в SQLite с помощью LLM, о чём достаточно было шумно в определённых интернетах, которые сопровождались рассказами, что скоро всех "ибешни...
Alex Sherbakov
5
program test; {$mode delphi} procedure proc(v: int32); overload; begin end; procedure proc(v: int64); overload; begin end; var x: uint64; begin proc(x); end. Уж не знаю...
notme
6
Как передать управляющий символ в открытую через CreateProcess консоль? Собсна, есть процедура: procedure TRedirectThread.WriteData(Data: OEMString); var Written: Cardinal;...
Serjone
6
вы делали что-то подобное и как? может есть либы готовые? увидел картинку нокода, где всё линиями соединено и стало интересно попробовать то же в ddl на lua сделать. решил с ч...
Victor
8
Ребят в СИ можно реализовать ООП?
Николай
33
Подскажите пожалуйста, как в CustomDrawCell(Sender: TcxCustomGridTableView; ACanvas: TcxCanvas; AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean); получить наз...
A Z
7
Карта сайта