Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monadic if else

Tags:

haskell

monads

I am new to Haskell and want to generate an Arbitrary tree.
So my first idea is to create an arbitary bool, if it is true then return an empty tree and else create a non-empty one:

instance (Arbitrary a) => Arbitrary (BinaryTree a)
  arbitrary = do
    createNonEmpty <- arbitrary
    if createNonEmpty 
      then return Nil
      else generateNonEmptyTree

But this pattern of creating the bool and use it just for that if seems a bit odd and it feels like there should be a more idiomatic way.
Is there already some kind of "monadic if" in the standard library that I could use like

arbitrary = ifM arbitrary (return Nil) (generateNonEmptyTree)

Or what else is the most idiomatic way to solve this?

like image 321
danielspaniol Avatar asked Dec 10 '22 07:12

danielspaniol


2 Answers

For QuickCheck in particular, I'd use oneof:

arbitrary = oneof [return Nil, generateNonEmptyTree]

It does essentially what you propose in your question (generate a one-off value, then use it immediately):

oneof :: [Gen a] -> Gen a
oneof [] = error "QuickCheck.oneof used with empty list"
oneof gs = choose (0,length gs - 1) >>= (gs !!)

But since it's a library function, this means you don't have to see the one-off values in your own code.

like image 143
Daniel Wagner Avatar answered Dec 29 '22 01:12

Daniel Wagner


My general solution to the "use once binding" is -XLambdaCase:

instance (Arbitrary a) => Arbitrary (BinaryTree a)
  arbitrary = arbitrary >>= \case
    True  -> return Nil
    False -> generateNonEmptyTree

Alternately, you could use something like

bool :: a -> a -> Bool -> a
bool f _ False = f
bool _ t True = t

(Bool's equivalent to either or foldr)

instance (Arbitrary a) => Arbitrary (BinaryTree a)
  arbitrary = bool generateNonEmptyTree (return Nil) =<< arbitrary
like image 34
rampion Avatar answered Dec 29 '22 01:12

rampion