Я изучал использование более newtype
оберток в моем коде для создания более разных типов. Я также делаю много дешевой сериализации с помощью Read/Show, особенно в виде простой формы сильно типизированного конфигурационного файла. Я столкнулся с этим сегодня:Экземпляр класса типа, который не используется при формировании структуры данных
Пример начинается, как это, и я определяю простую NewType, чтобы обернуть вокруг Int, наряду с именем поля для разворачивания:
module Main where
import Debug.Trace (trace)
import Text.Read (readEither)
newtype Bar = Bar { unBar :: Int }
deriving Show
Пользовательский экземпляр для чтения одного из них из простой синтаксис Int. Идея здесь заключается в том, что было бы здорово, если бы вместо «Bar {unBar = 42» «42» было добавлено «42» »
Этот экземпляр также имеет трассировку« logging », чтобы мы могли видеть, когда этот экземпляр действительно используется при наблюдении за проблемой.
instance Read Bar where
readsPrec _ s = [(Bar i, "")]
where i = read (trace ("[debug \"" ++ s ++ "\"]") s)
Теперь другой тип, содержащий бар. Этот будет просто автоматически выводить Read.
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO()
main = do
Десериализация типа Bar в одиночку прекрасно работает и использует экземпляр Read выше
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
Но по какой-то причине Foo, содержащей панель и автоматически выводится в Read, не сверлить вниз и собирания Экземпляры Бар! (Обратите внимание, что сообщение отладки из трассировки не отображается либо)
print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
putStrLn ""
Так хорошо, как насчет шоу формы по умолчанию для бара, должны соответствовать по умолчанию Read правильно?
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
in a test project on darcshub
Это очень хороший вопрос. Мне нравится, насколько легко вы сделали это для того, чтобы кто-то начал отлаживать ваш код. Надеюсь, мой ответ поможет определить конкретную проблему, с которой вы сталкиваетесь. В остальном я бы не рекомендовал использовать «Чтение» для чего-то большее, чем отладку, а затем убедиться, что «прочитал». show = id'. Я бы поместил свою конфигурацию в JSON (и использовал 'aeson' для кодирования/декодирования), или (если вы настаиваете на настраиваемом парсере) используйте что-то вроде' attoparsec' или 'megaparsec'. 'Read' - феноменально неэффективный синтаксический анализатор, потому что он готов отступить в любом месте. – Alec
Вы ошибаетесь: производный экземпляр для 'Foo' * is * использует экземпляр' Read' для 'Bar', который вы написали! Просто потому, что экземпляр «Foo» терпит неудачу, прежде чем он пытается заставить значение «Бар» (следовательно, никогда не заставляет thunk с «трассировкой» в нем), потому что «Бар» неправильно сообщает, что он потреблял все оставшиеся данные и поэтому читатель 'Foo' не видит' '', он должен преуспеть. –
@Alec Я не рассматривал использование JSON для конфигов. Сохраняет типизацию и иерархическую структуру. И тогда вы получите файл конфигурации, который можно использовать другими языками/системами. Я изучу это с помощью новых типов. Благодаря! – dino