haskell — состояние, подготовленное при запуске приложения и считанное из без параметров

#haskell

#haskell

Вопрос:

Я заканчиваю небольшое приложение с графическим интерфейсом на Haskell и прямо сейчас добавляю поддержку переводов. В Linux это просто, я могу использовать hgettext, и он предоставляет мне

 getText :: String -> IO String
 

и на самом деле авторы даже рекомендуют использовать его именно так:

 __ :: String -> String
__ = unsafePerformIO . getText
 

Меня это не шокирует, ведь все тексты отображаются только на экране, это все побочные эффекты, и обычно они не меняются во время выполнения приложения. Я мог бы обойтись без unsafePerformIO, но я думаю, что это нормально, хотя я никогда не нуждался в этом до сих пор в Haskell.

Однако моя проблема в том, что я не могу заставить gettext работать в Windows, и поэтому я решил развернуть свою собственную систему для Windows. Это должно быть довольно легко для моих очень умеренных потребностей. Я просто хочу проанализировать файлы PO и сделать себя Map String String , и у меня может быть моя функция для получения переводов. Поэтому при запуске я бы узнал текущий язык и прочитал файлы перевода… Но тогда мне пришлось бы передавать это Map String String повсюду в моей программе. Для каждого диалогового окна, а затем для каждой маленькой функции, которая будет спрашивать пользователя, обязательно ли он удалить этот элемент и так далее… Я думаю, что перенос всей программы в монаду чтения был бы абсолютно излишним.

Я читал о запоминании, также в изменяемом состоянии верхнего уровня, но решения кажутся излишними. Я мог бы сгенерировать несколько хэшей во время сборки с помощью довольно серьезного шаблона haskell magic, но это тоже звучит неправильно…

Я бы не был шокирован каким-то глобальным состоянием для этой функции с каким-то IORef (может быть, я должен быть …), Но я даже не уверен, как я бы это закодировал…

Любые подсказки о том, что я мог бы сделать в этом конкретном случае?

Комментарии:

1. На самом деле я недавно работал над API для выполнения простых переводов, и мой подход был, по сути ReaderT (Map String String) m a , решением. Я не думаю, что неразумно заставлять все ваши функции, связанные с пользовательским интерфейсом, жить внутри монады чтения, просто учитывайте фактическое поведение чистых функций.

2. Спасибо за совет. Может быть, мне стоит попробовать. На самом деле я еще не успел толком изучить monad transformers, и я заканчиваю свой проект, и я считаю Windows побочным вариантом, основным случаем которого являются linux и gettext… Но да, это решение определенно звучит лучше… Тем не менее, учитывая контекст, я, вероятно, пойду «уродливым» путем, если не будет относительно удобного приятного способа.

3. Ваш пользовательский интерфейс, вероятно, уже происходит в какой-то монаде, не так ли? Почему добавление к нему функции чтения является излишеством?

4. В настоящее время все это находится в монаде ввода-вывода, некоторые фрагменты в Render монаде из Каира.

Ответ №1:

Довольно часто используется стандартная идиома для изменяемого состояния верхнего уровня:

 translations :: IORef (Map String String)
{-# NOINLINE translations #-}
translations = unsafePerformIO (newIORef Map.empty)
 

NOINLINE Прагма важна для того, чтобы убедиться, что она не дублируется путем встраивания.

В вашем случае сигнатура типа не имеет большого значения, но в других случаях необходимо убедиться, что она мономорфна, иначе она все равно будет дублироваться на каждом сайте использования.

Затем вам нужно будет убедиться, что вы инициализируете его некоторыми полезными данными, прежде чем что-либо сможет его прочитать.

В идеале вы бы просто превратили его в Map String String без IORef и просто вызвали unsafePerformIO (read_translations ...) определение, но это будет зависеть от того, были ли все параметры, необходимые для этого, доступны в этот момент.

Комментарии:

1. ну, я был на этой странице, я не могу полностью понять, как я мог пропустить этот код. Но мне нравится идея, которая, я думаю, никогда бы не пришла мне в голову, сразу заполнить ее правильными значениями… Мне нужно выполнить вызов FFI для win32 api и прочитать некоторые файлы, чтобы заполнить эту карту. Заполнение всего этого в этом вызове верхнего уровня unsafePerformIO может быть проблематичным при обработке ошибок.. Но тогда, если я не смогу выполнить эти действия, ничего хорошего все равно не произойдет, так что, черт возьми, я могу в конечном итоге сделать это так.

2. Если вызов завершится неудачно, ошибка будет просто сохранена в значении верхнего уровня и будет выдаваться каждый раз, когда что-то еще пытается использовать это значение. Часть магии отложенной оценки 🙂

3. … таким образом, вы могли бы перехватить его и более изящно завершить работу при запуске или, возможно, по умолчанию использовать известный язык?