2

I am currently in need of a bit of brain training and I found this article on Haskell and Monads

I'm having trouble with exercise 7 re. Randomised function bind.

To make the problem even simpler to experiment, I replaced the StdGen type with an unspecified type. So instead of...

bind :: (a -> StdGen -> (b,StdGen)) -> (StdGen -> (a,StdGen)) -> (StdGen -> (b,StdGen))

I used...

bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))

and for the actual function impelemtation (just straight from the exercise)

bind f x seed = let (x',seed') = x seed 
 in f x' seed'

and also 2 randomised functions to trial with:

rndf1 :: (Num a, Num b) => a -> b -> (a,b)
rndf1 a s = (a+1,s+1)
rndf2 :: (Num a, Num b) => a -> b -> (a,b)
rndf2 a s = (a+8,s+2)

So with this in a Haskell compiler (ghci), I get...

:t bind rndf2
bind rndf2 :: (Num a, Num c) => (c -> (a, c)) -> c -> (a, c)

This matches the bind curried with rndf2 as the first parameter.

But the thing I don't understand is how...

:t bind rndf2 . rndf1

Suddenly gives

bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)

This is the correct type of the composition that we are trying to produce because

bind rndf2 . rndf1

Is a function that:

  1. takes the same parameter type(s) as rndf1
  2. AND takes the return from rndf1 and pipes it as an input of rndf2 to return the same type as rndf2

rndf1 can take 2 parameters a -> c and rndf2 returns (a, c) so it matches that a composition of these function should have type:

bind rndf2 . rndf1 :: (Num a, Num c) => a -> c -> (a, c)

This does not match the naive type that I initially came up with for bind

bind f :: (a -> b -> (c, d)) -> (c, d) -> (e, f)

Here bind mythically takes a function that takes two parameters and produces a function that takes a tuple in order that the output from rndf1 can be fed into rndf2

  1. why the bind function needs to be coded as it is
  2. Why the bind function does not have the naive type
asked Jun 26, 2012 at 18:19
2
  • 1
    What do you mean by "the correct type that we are eventually heading for"? If you mean type of unit, then it's wrong because unit :: a -> c -> (a,c) (note the missing Num contexts). Commented Jun 26, 2012 at 21:16
  • @Vitus - I have added some extra clarification as to what types I am talking about together with a clarification of my actual question. Esentially my confusion is the same as Chris Bogart's from the original article's comments linked here Commented Jun 27, 2012 at 18:09

1 Answer 1

1

bind should take two functions. Your "initial" type signature takes one function and one tuple and produces a tuple. That's not really applicable to this problem: rndf1 is not a tuple, it is a function. Once parameters have been applied to it, only then will rndf1 a s become a tuple. So bind needs to be a function that takes two functions.

The mysterious force that pipes a tuple into a function that takes two parameters is in the definition of bind.

bind :: (a -> c -> (b,c)) -> (c -> (a,c)) -> (c -> (b,c))
bind f x seed = let (x',seed') = x seed 
 in f x' seed'

In this definition, both f and x are functions (maybe this would be more clear if you renamed x to g.)

Take let (x',seed') = x seed. x seed produces a tuple. On the left side of the equal sign, the individual elements of that tuple are given new names, and then on the next line those names are passed to the function f.

So, it may help to break down the expression bind rndf2 . rndf1.

Remember that all functions actually have exactly one parameter. The type of rndf1 could be written most accurately as (Num a , Num b) => a -> (b -> (a,b)). This is very helpful to understand what follows.

Another thing that would help is to think of how you would use this expression with parameters. Such as: (bind rndf2 . rndf1) a s.

Since all functions have one parameter, they're sent one by one to the inside of the parentheses. First: ((bind rndf2 . rndf1) a) s.

Now, you can rewrite the expression without the . operator: (bind rndf2 (rndf1 a)) s. a is passed to rndf1 because that is how . works: the function on the right side of the dot takes the input and then passes its output to the function on the left side.

You'll see that the type signature of rndf1 a matches the second parameter of bind. So then applying the parameters rndf2 and (rndf1 a) to bind:

bind rndf2 (rndf1 a) seed = let (x', seed') = (rndf1 a) seed
 in rndf2 x' seed'

Now you have a function inside the parentheses that only takes one seed parameter. So you can take the s and apply it to the function:

bind rndf2 (rndf1 a) s = let (x', seed') = (rndf1 a) s
 in rndf2 x' seed'
answered Jun 27, 2012 at 21:08
Sign up to request clarification or add additional context in comments.

1 Comment

Just spent the last few days thinking about the answer. I think I've nearly got it - When you say Remember that all functions actually have exactly one parameter. are you taking about the 2 parameters for the (.) operator?

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.