Я пытался использовать в своем коде больше newtype
оберток, чтобы создать более различные типы. Я также часто делаю дешевую сериализацию с использованием Read / Show, особенно в виде простой формы строго типизированного файла конфигурации. Я столкнулся с этим сегодня:
Пример начинается так, и я определяю простой новый тип для обтекания Int вместе с именованным полем для разворачивания:
module Main where
import Debug.Trace ( trace )
import Text.Read ( readEither )
newtype Bar = Bar { unBar :: Int }
deriving Show
Пользовательский экземпляр для чтения одного из них с помощью простого синтаксиса Int. Идея здесь в том, что было бы здорово иметь возможность поместить "42" в файл конфигурации вместо "Bar {unBar = 42}".
Этот экземпляр также имеет «ведение журнала» трассировки, чтобы мы могли видеть, когда этот экземпляр действительно используется, при наблюдении за проблемой.
instance Read Bar where
readsPrec _ s = [(Bar i, "")]
where i = read (trace ("[debug \"" ++ s ++ "\"]") s)
Теперь еще один тип, содержащий Bar. Этот будет просто автоматически извлекать Read.
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO ()
main = do
Десериализация только типа Bar работает нормально и использует экземпляр Read выше.
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
Но по какой-то причине Foo, содержащий Bar и автоматически производный от Read, не выполняет детализацию и не выбирает экземпляры Bar! (Обратите внимание, что сообщение об отладке из трассировки также не отображается)
print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
putStrLn ""
Итак, хорошо, а как насчет формы отображения по умолчанию для панели, которая должна совпадать с правом чтения по умолчанию?
print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)
Нет! Тоже не работает !! Опять же, нет отладочного сообщения.
Вот результат выполнения:
$ stack exec readbug
[debug "42"]
Right (Bar {unBar = 42})
Left "Prelude.read: no parse"
Left "Prelude.read: no parse"
Мне это кажется ошибочным, но я хотел бы услышать, что я делаю это неправильно.
Доступен полностью рабочий пример приведенного выше кода. См. Файл src/Main.lhs
в тестовом проекте на darcshub
Read
для чего-либо, кроме отладки, - а затем убедитесь, чтоread . show = id
. Я бы поместил свою конфигурацию либо в JSON (и использовалaeson
для кодирования / декодирования), либо (если вы настаиваете на настраиваемом парсере) использовал что-то вродеattoparsec
илиmegaparsec
.Read
- феноменально неэффективный синтаксический анализатор, потому что он готов возвращаться куда угодно. - person Alec   schedule 16.12.2016Foo
использует экземплярRead
дляBar
, который вы написали! Просто экземплярFoo
терпит неудачу, прежде чем он потрудится принудительно установить значениеBar
(следовательно, никогда не заставляет преобразователь сtrace
в нем), потому чтоBar
неверно сообщает, что он потребил весь оставшийся ввод, и поэтому читательFoo
не видит}
это необходимо для успеха. - person Daniel Wagner   schedule 16.12.2016