LINUX.ORG.RU

Как запомнить результат из IO ()?

 


0

1

Есть, условно говоря, такой код:

main :: IO ()
main = do
  conf <- Con.load [Con.Required "service.cfg"]
  let subconf = Con.subconfig "lala" conf
  listen <- Con.lookupDefault  "127.0.0.1" subconf "listen" :: IO String
  port <- Con.lookupDefault 4002 subconf "port" :: IO Int
  runTCPServer …


ldapURI = "ldap://host/"
ldapUser = "cn=admin"
ldapPWD = "123"

ldapBDN = Just "ou=Org"
ldapAttrs = LDAPAttrList ["param1", "param2"]


ldapExecuteQuery :: ByteString -> String -> IO AnswerEmailCheck
ldapExecuteQuery m qwr = do
  conn <- ldapInitialize ldapURI
  ldapSimpleBind conn ldapUser ldapPWD
  result <- ldapSearch conn ldapBDN LdapScopeSubtree (Just qwr) ldapAttrs False
  return $ getRes m result

myConnectionRedis :: ConnectInfo
myConnectionRedis = defaultConnectInfo {connectHost = "127.0.0.1"}

condRedisGet :: MonadIO m => Conduit MailMqouta m ByteString
condRedisGet = awaitForever $ \(mail, mquota) -> do
  conn <- liftIO $ Database.Redis.connect myConnectionRedis
  result <- liftIO $ runRedis conn $ do
    res <- get . createRedisQuery $ mail
    case res of
…

Суть проблемы такова: я хочу вынести константы типа myConnectionRedis, ldapURI и прочего во внешний конфиг. То, как оно может быть исполнено, нарисовано в main.

При этом я не пойму, как написать код, чтобы избежать многократного перечитывания конфига при вызовах функций condRedisGet и прочих, где используются данные константы.

Функции же эти вызываются очень часто, на каждый кусок данных.

Возможно, кстати, что мой подход к написанию кода ужасен, тогда прокомментируйте и скажите, что именно не так.

Заранее благодарю за советы.


Ответ на: комментарий от qnikst

ну и соответсвенно свои конфиги под dlap и reddis, ещё есть интересная гугловая либа hflags, но там все параметры являются параметрами командной строки (политика гугла). Особенность либы то, что ты в каждом модуле можешь указывать локальные опции, а либа автоматически соберёт их Main файле.

qnikst ★★★★★
()

многократного перечитывания конфига

Зачем, же. Читай конфиг один раз в начале, а потом передавай прочитанные значения в фунции.

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

data Config = Config { redisConnection :: ConnectInfo
                     , ldapURI         :: String
                     , ldapUser        :: String
                     ...
                     }

И будешь явно передавать этот конфиг всем функциям.

Это тоже не лучший подход, потому что нужно явно передавать. Поэтому придумали монаду Reader, в которую можно засунуть этот конфиг и читать его из каждой функции. Но потом тебе нужно будет скомбинировать монаду Reader и монаду IO, для этого понадобяться монадные трансформеры. Почитай, http://habrahabr.ru/post/184722/ и Real World Haskell про монады и монадные трансформеры. Монадные трансформеры позволяют построить стек из монад, на котором обычно держится архитектура приложения. В твоем случае это будет выглядеть примерно вот так:

main :: IO ()
main = do
  conf <- readConfig
  runReaderT (ldapExecuteQuery m qwr) conf -- происходит вызов ldapExecuteQuery с неявной передачей конфига

data Config = Config { redisConnection :: ConnectInfo
                     , ldapURI         :: String
                     , ldapUser        :: String
                     ...
                     }

readConfig :: IO Config
readConfig = do
   connHost <- Con.load "connHost"
   let redisCon ConnectInfo {connectHost = connHost}
   ldapURI <- Con.load "ldapURI"
   ldapUser <- Con.load "ldapUser"
   return Config redisCon ldapURI ldapUser

type App = ReaderT Config IO

ldapExecuteQuery :: ByteString -> String -> App AnswerEmailCheck
ldapExecuteQuery m qwr = do
  conf{..} <- ask -- достаем конфиг
  conn <- ldapInitialize ldapURI -- конфиг передается неявно
  ldapSimpleBind conn -- конфиг передается неявно
  result <- ldapSearch conn ldapBDN LdapScopeSubtree (Just qwr) ldapAttrs False -- то же самое
  return $ getRes m result

ldapInitialize :: App Connection
ldapInitialize = undefined

ldapSimpleBind :: App Connection
ldapSimpleBind = undefined

ldapSearch :: App Connection
ldapSearch = undefined

zinfandel ★★
()
Последнее исправление: zinfandel (всего исправлений: 2)

При этом я не пойму, как написать код, чтобы избежать многократного перечитывания конфига при вызовах функций condRedisGet и прочих, где используются данные константы.

Хаскель должен все прокешировать. Con.load ведь «чистая» функция, параметром принимает имя файла, которое константа.

crowbar
()
Ответ на: комментарий от hateyoufeel

Ещё точнее, я этого вообще не хочу.

HolyBoy
() автор топика
Ответ на: комментарий от zinfandel

Спасибо большое тебе и всем ответившим. Так и сделаю.

HolyBoy
() автор топика
Ответ на: комментарий от Aswed

А чем собственно плохи IORef? Тем что все в IO? Ну так ReaderT MyConfig IO тоже в IO будет работать.

  • неатомарный (между forkIO / writeIORef)
  • IORef - мутабельный (через writeIORef), автор хочет «константу»
  • переменную типа IORef
    • или надо всё равно явно передавать в функции (тогда можно сразу значение IORef-а передавать)
    • или придется использовать unsafePerformIO (но это никто правильно делать не умеет).
sf ★★★
()

Насколько я понял, используется пакет Configurator. У него в нутрях неонка^W IORef. Т.е. когда ты делаешь conf <- Con.load ..., конфиг читается, парсится и складывается в IORef. Поэтому, технически достаточно передавать везде где надо эту переменную conf в качестве аргумента (или через ReaderT, что то же самое). А там, где нужно значение из конфига, делать lookupDefault. Внутри ioref-а лежит HashMap, поэтому доступ по lookup O(1) (по крайней мере, теоретически должен быть). Другой вариант — написать ADT для конфига, сразу после load записать информацию из конфига в этот ADT и дальше передавать везде уже этот ADT.

Portnov
()
Ответ на: комментарий от Aswed

Только тем же, чем и синглтоны или глобальные переменные - лишний источник багов и не скейлится на случай многих конфигов. PS замена IORef на IVal (можно записать данные только 1 раз) лучше но часть проблем останется

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Ну.. unsafePerformIO с NOINLINE же сработает :)

В данном случае - да.

Но в общем, если таких unsafePerformIO больше одного и они все с одним типом, то -fcse всё равно может объединить два одинаковых NOINLINE биндинга в один, так что еще надо добавлять -fno-cse в содержащий модуль.

И это только требования для текущей версии ghc. Остальные haskell-компиляторы могут глючить по-другому :]

Самое поганое в том, что неправильное использование unsafePerformIO может проявляться только на более свежих версиях ghc (из-за лучшего инлайнера или лучше анализатора дублирующегося кода).

Этот баг дожил до 7.8:

http://gentoohaskell.wordpress.com/2014/06/16/unsafeperformio-and-missing-noi...

По-хорошему -fno-cse в те модули тоже стоит добавить :]

sf ★★★
()
Ответ на: комментарий от sf

А кстати да, мы напарывплись на такое, в итоге пока не убрали почти все вызовы unsafePerformIO ловили адские ошибки, но в этом случае мы чистые ССЗБ.

qnikst ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.