我正在尝试编写一个 Monad 来跟踪一段代码正在使用哪些 i18n 键。这个想法是预先以有效的方式预取所有这些键的值,而不是在代码执行期间最终出现“N+1”的情况。
这是我到目前为止所拥有的(代码在最后给出)。我该如何为此编写 Monad 实例?有可能吗?还有其他想法可以做到这一点吗?只要 devex/人体工程学是可管理的,我也可以在类型级别跟踪按键。
我思考这个问题的另一种方式是,我需要代码以“两个阶段”运行:
[I18nKey]
- 代码所依赖的 i18n 键列表和 [(I18nKey, I18nStr)] -> m Text
一个未应用的函数,需要关联列表中的 i18n 键来生成最终值。[I18nKey] -> [(I18nKey, I18nStr)]
并将它们应用于函数以获得最终值。如何以符合人体工程学的方式用 Haskell 代码表达上述内容?
import UnliftIO.IORef
import Data.Text
import Control.Monad.Reader
import qualified Data.List as DL
type I18nKey = Text
type I18nStr = Text
data I18nEnv m a = I18nEnv
{ envI18nExpectedKeys :: [I18nKey]
, envI18nVal :: ReaderT [(I18nKey, I18nStr)] m a
}
instance (Functor m) => Functor (I18nEnv m) where
{-# INLINE fmap #-}
fmap f env =
env{envI18nVal=fmap f (envI18nVal env)}
instance (Applicative m) => Applicative (I18nEnv m) where
{-# INLINE pure #-}
pure x =
I18nEnv { envI18nExpectedKeys = [], envI18nVal = pure x }
{-# INLINE (<*>) #-}
(<*>) fn a = I18nEnv
{ envI18nExpectedKeys=(envI18nExpectedKeys fn <> envI18nExpectedKeys a)
, envI18nVal=(envI18nVal fn) <*> (envI18nVal a)
}
这是不可能的。暂时忘记
ReaderT
,您的类型基本上是 ([I18nKey], m a)
;为了使它成为一个 monad,你必须实现 join :: ([I18nKey], m ([I18nKey], m a)) → ([I18nKey], m a)
,但是一般来说没有办法从 [I18nKey]
下提取内部 m
。
将通用单子
m
与 Writer [I18nKey]
组合的正确方法是使用 WriterT [I18nKey] m
,它会转换为 m ([I18nKey], a)
,但这似乎不适合您的用例。
你确定你需要一个 monad 吗?看看你打算如何使用这个 monad 的例子会有所帮助。