cue-lang / cue

The home of the CUE language! Validate and define text-based and dynamic configuration
https://cuelang.org
Apache License 2.0
5.16k stars 297 forks source link

pkg: functionality for generating pseudo random number(s) #1646

Open ningan123 opened 2 years ago

ningan123 commented 2 years ago

Is your feature request related to a problem? Please describe.

I wanna generate a random number in .cue file. However, I can't find a function to do it.

Describe the solution you'd like

I want a function that can generate a random number in .cue file.

test.cue

import "math/rand" or "(whatever)/rand" 

a: rand.Int(rand.Reader, big.NewInt(128))
b: rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(100000000))

run cue export test.cue

Then the output is

a: "a random number"
b: "a random number"

I just want a function that can generate a random number, the function name is not important.
Thank u!

Describe alternatives you've considered

Additional context

rogpeppe commented 2 years ago

As CUE is by design a hermetic language, this isn't something that can be incorporated directly.

If you just want a known number of random numbers, there is an option to inject 128-bit random numbers into the CUE (see cue help injection). For example:

#aRandomNumber: int @tag(seed,var=rand)

You'll have to evaluate with the -T command line flag for this to work though.

It is just about technically possible to use that single random number to generate an arbitrary set of other random numbers, but I wouldn't recommend doing so. It might be that a new addition to the standard library could make that easier, but that depends somewhat on your use case. With that in mind, what are you actually trying to do? i.e. what's your use case for generating random numbers?

verdverm commented 2 years ago

Maybe something like tool/rand would be acceptable?

https://docs.hofstadter.io/data-flow/schemas/tasks/gen/

ningan123 commented 2 years ago

As CUE is by design a hermetic language, this isn't something that can be incorporated directly.

If you just want a known number of random numbers, there is an option to inject 128-bit random numbers into the CUE (see cue help injection). For example:

#aRandomNumber: int @tag(seed,var=rand)

You'll have to evaluate with the -T command line flag for this to work though.

It is just about technically possible to use that single random number to generate an arbitrary set of other random numbers, but I wouldn't recommend doing so. It might be that a new addition to the standard library could make that easier, but that depends somewhat on your use case. With that in mind, what are you actually trying to do? i.e. what's your use case for generating random numbers?

Actually, with the random number mentioned above, 1) I want a 32bit string, the format is [0-9a-z]{32}. So, I want a random number to select a character randomly between [0-9a-z] for 32 times. 2) I want to choose a number in [A, B] randomly. eg.[100, 200]

ningan123 commented 2 years ago

As CUE is by design a hermetic language, this isn't something that can be incorporated directly.

If you just want a known number of random numbers, there is an option to inject 128-bit random numbers into the CUE (see cue help injection). For example:

#aRandomNumber: int @tag(seed,var=rand)

You'll have to evaluate with the -T command line flag for this to work though.

It is just about technically possible to use that single random number to generate an arbitrary set of other random numbers, but I wouldn't recommend doing so. It might be that a new addition to the standard library could make that easier, but that depends somewhat on your use case. With that in mind, what are you actually trying to do? i.e. what's your use case for generating random numbers?

With your suggestion, I have generated a 128bit random number successfully. Now, I wanna change it to the string format. However, the following function is int64, the generated number is int128. It doesn't match. How can I do it? Thank u.

func FormatInt(i int64, base int) string
ningan123 commented 2 years ago

Maybe something like tool/rand would be acceptable?

https://docs.hofstadter.io/data-flow/schemas/tasks/gen/

Actually, with the random number mentioned above, 1) I want a 32bit string, the format is [0-9a-z]{32}. So, I want a random number to select a character randomly between [0-9a-z] for 32 times. 2) I want to choose a number in [A, B] randomly. eg.[100, 200]

My go project needs to import the cue package, like this:

// test.go 
import (
    "fmt"
    "cuelang.org/go/cue/ast"
    "cuelang.org/go/cue/load"
    "cuelang.org/go/cue/cuecontext"
)

func main(){
    ctx := cuecontext.New() 
    entryPoints := []string{"test.cue"}
    b := ctx.BuildInstance(...)
    ...
    fmt.Printf("b: %s\n", b)
}
// test.cue
// I'm not familiar with cue, so I write my thought in go.

str := "0123456789abcdefghijklmnopqrstuvwxyz"
var token string
for i := 0; i < 32; i++ {   
     rand:= rand.Intn(32) // 0-32
     token += str[rand]
}

When I run go run test.go

I expect to see token: "781292db7bc3a58fc5f07e" // 32bit just an example

Question: 1) Does the cue support the rand function? a random number is enough. 2) Does the syntax of the cue support the thought I write in the test.cue?

Thank u so much!

verdverm commented 2 years ago

A few thoughts

One option is to generate the random value from Go and Fill it into a Value. This means you will have to write a Go program and use CUE's GoAPI.

myitcv commented 2 years ago

@mvdan and I discussed, and we'll try to bring together the various threads in this issue.

Firstly, on the question of "how do I get random values in CUE?", the answer depends on the context and what you're trying to solve:

Here is an example that shows all three in action:

exec go mod tidy

# cue eval example. Note we need to
# explicitly provide -T for cue eval.
# This ensures that cue eval is hermetic
# by default (same applies for def, export
# and vet).
exec cue eval -T -e u2

# cue cmd example. Note that -T
# is enabled by default for cue cmd. This
# is because cue cmd is by definition not
# hermetic, i.e. the tasks can have arbitrary
# side effect etc.
exec cue cmd print

# go API example
exec go run main.go

-- go.mod --
module mod.com

go 1.18

require cuelang.org/go v0.4.3

-- main.go --
package main

import (
    "fmt"

    "cuelang.org/go/cue"
    "cuelang.org/go/cue/cuecontext"
    "cuelang.org/go/cue/load"
)

func main() {
    ctx := cuecontext.New()
    opts := &load.Config{
        TagVars: load.DefaultTagVars(),
    }
    bps := load.Instances([]string{"."}, opts)
    vs, err := ctx.BuildInstances(bps)
    if err != nil {
        panic(err)
    }
    u2 := vs[0].LookupPath(cue.MakePath(cue.Str("u2")))
    fmt.Printf("u2: %v\n", u2)
}
-- x.cue --
package x

import "uuid"

import "strings"

x:  int @tag(x, var=rand)
u:  uuid.FromInt(x)
u2: strings.Replace(u, "-", "", -1)
-- x_tool.cue --
package x

import "tool/cli"

command: print: cli.Print & {
    text: "u2: \(u2)"
}

Each of the three examples uses the CUE in x.cue.

The first case of normal CUE evaluation uses injection to introduce a random value into evaluation. Because CUE evaluation is designed to be hermetic, the -T flag is explicitly required here to signal that as the caller you know you are injecting (random) values that might influence the evaluation result.

The second example using the tooling layer simply wraps that same CUE with a command specification in x_tool.cue. Note that in this case the -T flag is not required. Because the tooling layer by design allows you to evaluate code that has side effects, introduces random values etc. We again use the rand tag var, but we could equally use the result of a task as the source of randomness.

The third example of the Go API usage simply shows how to do the equivalent of the first case but via the Go API. It also hints at how you could specify a different random value by using a source other than load.DefaultTagVars().

I think from looking at your examples you're tending towards the third case?

The one thing I will flag is that looking at your responses, it appears you might be better using the uuid tag var instead of rand. Because then as you can see from x.cue, you could simply avoid the step of creating a UUID yourself. However, for creating a sequence of UUIDs a sequence of random numbers is likely a better starting point.

That said, I think the missing functionality here is the ability, as @rogpeppe highlighted, to create a pseudo-random number from a seed. We don't have anything like math.Rand() right now, which means that you need to resort to some of the gymnastics that @rogpeppe highlighted in https://github.com/cue-lang/cue/issues/1646#issuecomment-1104104746.

So in conclusion, I think this issue does have merit as a feature request for such pseudo random number builtin, one that works with *big.Int so as to be compatible with the rand tag var. This would allow you to create a sequence of random numbers using a cryptographically random seed, or a sequence of UUIDs for that matter.

On that basis I'll re-open this issue.

Please shout if I've missed anything here.

myitcv commented 2 years ago

Building on my last comment, I'm not totally clear that we strictly need the ability to generate a sequence of random numbers; generating a single pseudo random number will suffice because creating a sequence/map of random numbers is relatively straightforward when required:

package x

import "list"

_x: int @tag(x, var=rand)
l: [ for i in list.Range(0, 3, 1) {(*(l[i-1] ) | _x) / 2}]
_ks: ["m1", "m2", "m3"]
m: {
    for i, k in _ks {
        (k): (*(m[_ks[i-1]] ) | _x ) / 2
    }
}

In the example above I've substituted / 2 in place of a call to math.Rand() or whatever solution we come up with here. Hopefully the idea is clear though in terms of generating "the next number in the sequence".

That said, if a sequence-like function proves useful we could clearly add that too.