如何以及何时使用 State functor 和 State applicative?

How and when to use State functor and State applicative?

我看过代码中使用的 MaybeEither 仿函数(和应用函数),这是有道理的,但我很难想出 State 函子和应用。也许它们不是很有用,只是因为 State monad 需要仿函数和应用函数才存在?那里有很多关于它们的实现的解释,但没有任何在代码中使用它们的示例,所以我正在寻找它们如何单独使用的插图。

我能想到几个例子。

首先, State 的一个常见用途是管理一个计数器,目的是使某些“标识符”集唯一。因此,状态本身是一个 Int,主要的原始状态操作是检索计数器的当前值并递增它:

-- the state
type S = Int

newInt :: State S Int
newInt = state (\s -> (s, s+1))

仿函数实例是对不同类型的标识符使用相同计数器的简洁方法,例如某些语言中的术语级和类型级变量:

type Prefix = String
data Var = Var Prefix Int
data TypeVar = TypeVar Prefix Int

像这样生成新标识符的地方:

newVar :: Prefix -> State S Var
newVar s = Var s <$> newInt

newTypeVar :: Prefix -> State S TypeVar
newTypeVar s = TypeVar s <$> newInt

应用实例有助于编写由此类唯一标识符构造的表达式。例如,我在编写类型检查器时经常使用这种方法,它通常会使用新变量构造类型,如下所示:

typeCheckAFunction = ...
    let freshFunctionType = ArrowType <$> newTypeVar <*> newTypeVar
    ...

此处,freshFunctionType 是一种新的 a -> b 样式类型,具有新的类型变量 ab,可以传递到统一步骤。

其次State的另一个用途是管理随机数生成的种子。例如,如果你想要一个低质量但超快的 LCG 生成器,你可以写:

lcg :: Word32 -> Word32
lcg x = (a * x + c)
  where a = 1664525
        c = 1013904223

-- monad for random numbers
type L = State Word32

randWord32 :: L Word32
randWord32 = state $ \s -> let s' = lcg s in (s', s')

仿函数实例可用于使用纯转换函数修改 Word32 输出:

randUniform :: L Double
randUniform = toUnit <$> randWord32
  where toUnit w = fromIntegral w / fromIntegral (maxBound `asTypeOf` w)

而应用实例可用于编写依赖于多个 Word32 输出的原语:

randUniform2 :: L (Double, Double)
randUniform2 = (,) <$> randUniform  <*> randUniform

以及以合理自然的方式使用随机数的表达式:

-- area of a random triangle, say
a = areaOf <$> (Triangle <$> randUniform2 <*> randUniform2 <$> randUniform2)