Hi, I am trying to write a library to work with probability random variables.

It is easy to define what a random variable is using concepts: sampling from a 
random variable should return a value of a certain type, chosen according to a 
random input.

This is why I am using BlaxSpirit 
[random](https://github.com/BlaXpirit/nim-random) library.

In this library, a random number generator is defined as a concept as well. I 
am not sure how to define concepts using concepts, so for now I will stick with 
a concrete random number generator.

The definition I would like to give is then
    
    
    type RandomVar[A] = concept x
      var rng = initMersenneTwister(urandom(16))
      rng.sample(x) is A
    

The library should then provide some basic random variables and some means to 
combine them in more complex ones. In particular, I would like to be able to 
lift functions on values to functions on random variables: if I have a function 
`f: (A, B) -> C`, I can easily get a `g: (RandomVar[A], RandomVar[B]) -> 
RandomVar[C]`. Whenever I need to sample from `g(a, b)`, I can sample from `a`, 
sample from `b` and then apply `f`.

Now, I can define some basic random variables and it works nicely:
    
    
    type
      Constant[A] = object
        value: A
      Uniform = object
        a, b: float
      Discrete[A] = object
        values: ref seq[A]
    
    proc sample[A](rng: var RNG, c: Constant[A]): A = c.value
    
    proc sample(rng: var RNG, u: Uniform): float = u.a + (u.b - u.a) * 
rng.random()
    
    proc sample[A](rng: var RNG, d: Discrete[A]): A = 
rng.randomChoice(d.values[])
    
    when isMainModule:
      var rng = initMersenneTwister(urandom(16))
      let
        c = constant(3)
        u = uniform(2, 18)
        d = choose(@[1, 2, 3])
      
      echo(c is RandomVar[int]) # true
      echo(u is RandomVar[float]) # true
      echo(d is RandomVar[int]) # true
      
      echo rng.sample(c)
      echo rng.sample(u)
      echo rng.sample(d)
    

The problem arises when trying to combine random variables. Let me stick to 
functions of a single input. For this I need random variables represented as 
closures.

I can define
    
    
    type
      MyRNG = type(initMersenneTwister(urandom(16)))
      ProcVar[A] = object
        extract: proc(rng: var MyRNG): A
    
    proc sample[A](rng: var MyRNG, p: ProcVar[A]): A = p.extract(rng)
    

To lift a single variable function, I can define
    
    
    proc lift1[A, B](p: proc(a: A): B): proc(a: RandomVar[A]): ProcVar[B] =
      proc res(a: RandomVar[A]): ProcVar[B] =
        proc extract(rng: var MyRNG): B =
          let a1 = rng.sample(a)
          return p(a1)
        
        result.extract = extract
      
      return res
    

to be used like
    
    
    proc sq(x: float): float = x * x
    
    let s = lift1(sq)
    let x = s(u)
    

but this does not compile, since `RandomVar` is a concept. I can just use 
`ProcVar` in place of it, like this:
    
    
    proc lift1[A, B](p: proc(a: A): B): proc(a: ProcVar[A]): ProcVar[B] =
      proc res(a: ProcVar[A]): ProcVar[B] =
        proc extract(rng: var MyRNG): B =
          let a1 = rng.sample(a)
          return p(a1)
        
        result.extract = extract
      
      return res
    

but then to pass the other random variables to it, I need a converter
    
    
    converter toProcVar[A](r: RandomVar[A]): ProcVar[A] =
      proc extract(rng: var MyRNG): A = rng.sample(r)
      
      result.extract = extract
    

If I use this converter, the compiler goes into an infinite loop. If I change 
the converter into a `proc` and try to use it explicitly like this
    
    
    let
      s = lift1(sq)
      x = s(toProcVar[float](u))
    

the compiler just crashes.

What approach would you suggest to make the whole machinery work reasonably?

Reply via email to