让我们看看你的类型:
bind :: (a → StdGen → (b,StdGen)) → (StdGen → (a,StdGen)) → (b,StdGen)
现在,我 100% 同意你的第一点:
Bind 接受一个函数f :: a -> StdGen -> (b,StdGen)
和“输出” StdGen -> (a,StdGen)
。
但是你的第二个让我担心:
它适用f
于a
and StdGen
。
你从哪里得到类型的值a
?你从哪里得到类型的值StdGen
?
The answer to both of these questions is "you don't have one lying about"; however, since you do have a StdGen -> (a,StdGen)
lying about, you could get both if only you had one more StdGen
parameter. And that's where the extra parameter comes from.
Now, a slightly higher-level explanation. Part of the problem (I think) is that these type signatures are a bit too cluttered to read comfortably. We need some abstractions. What we're trying to model here are probability distributions, which we're modeling as their sampling functions. So, we can say a distribution over a
is a function that knows how to sample from the distribution and return an a
:
type Dist a = StdGen -> (a, StdGen)
Now, not all distributions are so flat as all that. For example, the Bernoulli distribution is "sort of" a Dist Bool
, but it's also parameterized on the probability of choosing False
. We can write its type thus:
bernoulli :: Double -> Dist Bool
So, we can model parameterized distributions as functions that return distributions; equivalently, we can think of functions that return distributions as parameterized distributions.
Now with this high-level interpretation in mind, the type of bind
becomes much more readable:
bind :: (a -> Dist b) -> (Dist a -> Dist b)
This says that bind
is the function that tells how to first sample from an a
distribution, then use that a
as a parameter when sampling from the b
distribution. Not only that, but with this type alias, it almost becomes unthinkable to write a type for bind
that doesn't have the "extra" argument.