Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to define arbitrary extensions for recursive datatypes? #270

Open
Fryie opened this issue May 22, 2018 · 5 comments
Open

How to define arbitrary extensions for recursive datatypes? #270

Fryie opened this issue May 22, 2018 · 5 comments

Comments

@Fryie
Copy link

Fryie commented May 22, 2018

Hi,
I'm looking for a way to add arbitrary to types that are recursive, e.g.

struct List {
   let head: Int
   let tail: [Int]
}

Knowing QuickCheck in Haskell, the impulse is to write

extension List {
   public static var arbitrary: Gen<List> {
       return Gen<List>.one(of: [
           Int.arbitrary.map { List(head: $0, tail: []) },
           return List.arbitary.flatMap { (list: List) in
               Int.arbitrary.map { (i: Int) in
                   return List(head: i, tail: list)
               }
           }
       ])
    }
}

knowing that the probability of this sequence ending after some finite number of elements is 1. But obviously, in a strict language like Swift this can't work, since List.arbitrary will just call itself endlessly. Short of having to somehow manage a maximum depth myself, is there anything I can do to emulate this?

@felix91gr
Copy link

Try running it. Since Gen<T>.one(...) picks the given options at random, the probability of giving you a Stack Overflow is 0. Unless you give it weights such that it's forced to always pick the recursive step.

Also, I'd recommend giving it a weight dependent on how deep you want the average List to be:

  • 50% recursion prob: 1/1000 prob that you'll get a length 10+ List.
  • 90% recursion prob: 1/3 prob that you'll get a length 10+ List.

@Fryie
Copy link
Author

Fryie commented May 22, 2018

Sorry, I probably didn't explain correctly. The problem is that all of the elements of the array passed to one(...) are evaluated strictly before the function is called. That's what causes the endless recursion. That wouldn't be a problem in a lazily evaluated language like Haskell.

I'm currently testing this workaround, which seems to be able to do the job at least for the time being:

extension Gen {
    public static func lazy(_ gen: @escaping () -> Gen<A>) -> Gen<A> {
        return Gen<Bool>.pure(true).flatMap { _ in
            return gen()
        }
    }
}

// then call with:
(Gen<List>.lazy { List.arbitrary }).map ...

@felix91gr
Copy link

Ah! Of course. I see, the compiler is trying to unroll the recursion right away.

Thanks for explaining it again ^^

Yes, making a lazy extension to it like you're doing is probably the best approach.

Maybe SwiftCheck needs a LazyGen? 🤔 I don't know enough about its design, so I couldn't say.

@jgongo
Copy link

jgongo commented Nov 8, 2019

I'd love to see such a functionality included in SwiftCheck. Swift in fact includes laziness to some extent with the use of autoclosures.

Unfortunately this seems to be available only with parameters, not as a general modifier, so I guess you can't have something like an [@autoclosure ()-> Gen<A>] so you could change the signature of one(of:) from:

public static func one<S : BidirectionalCollection>(of gs : S) -> Gen<A>
	where S.Iterator.Element == Gen<A>, S.Index : RandomType

to:

public static func one<S : BidirectionalCollection>(of gs : S) -> Gen<A>
	where S.Iterator.Element == @autoclosure () -> Gen<A>, S.Index : RandomType

Maybe they could add a one(of:) method with a variable number of @autoclosure arguments the same way zip methods are generated?

@jgongo
Copy link

jgongo commented Nov 11, 2019

BTW, I think the lazy extension could be as simple as:

extension Gen {
    public static func lazy(_ gen: @autoclosure () -> Gen<A>) -> Gen<A> {
        gen()
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants