providence
providence

Reputation: 29463

Does Haskell always know which 'return' to call?

I am defining an instance of a monad as follows:

data Something = Something a

instance Monad Something where
    return a = Something a        --Wraps a in 'Something', correct?
    m >>= f = do
        var <- m
        return $ f var            --I want this to pass var to f, then wrap the result
                                  --back up in the 'Something' monad with the return I
                                  --Just defined

The questions are ->

1: Are there any glaring errors/misconceptions with what I am doing?

2: Will Haskell know to call the return I have defined above from m >>= f

3: If I for some reason define another function

f :: Something a -> Something b
f x = do
    var <- x
    return $ doMagicTo x

Will return call the return I defined in the monad instance and wrap x in Something?

Upvotes: 8

Views: 574

Answers (3)

Tyler
Tyler

Reputation: 22116

  1. Close. The return is redundant here and you need to add a type parameter to your Something type constructor. Edit: This code is still wrong. The definition of >>= is circular. See the other answers for more information.

    data Something a = Something a
    
    instance Monad Something where
        return a = Something a
        m >>= f = do
            var <- m
            f var
    
  2. Since your definition for >>= is under the instance Monad Something where, >>='s type is Something a -> (a -> Something b) -> Something b. So it can tell that f var has to be of type Something b. The term to google here is "type inference"

  3. Yes. Again, this is type inference. If the compiler can't infer the type you want, it will tell you so. But usually it can.

Upvotes: 2

John L
John L

Reputation: 28097

There are a few big problems here.

First, a Monad instance must have kind * -> *. That means they need at least one type variable, where your Something doesn't have any. For comparison:

-- kind * -> *
Maybe
IO
Either String

-- kind *
Maybe Int
IO ()
Either String Double

See how each of Maybe, IO, and Either String need a type parameter before you can use them? With Something, there's no place for the type parameter to fill in. So you need to change your definition to:

data Something a = Something a

The second big problem is that the >>= in your Monad instance is wrong. You generally can't use do-notation because that just calls the Monad functions return and >>=. So you have to write it out without any monad functions, either do-notation or calling >>= or return.

instance Monad Something where
    return a = Something a        --Wraps a in 'Something'
    (Something m) >>= f = f m     --unwraps m and applies it to f

The definition of >>= is simpler than you expected. Unwrapping m is easy because you just need to pattern-match on the Something constructor. Also f :: a -> m b, so you don't need to worry about wrapping it up again, because f does that for you.

While there's no way to unwrap a monad in general, very many specific monads can be unwrapped.

Be aware that there's nothing syntactically wrong with using do-notation or >>= in the monad instance declaration. The problem is that >>= is defined recursively so the program goes into an endless loop when you try to use it.

(N.B. Something as defined here is the Identity monad)

For your third question, yes the return function defined in the Monad instance is the one that will be called. Type classes are dispatched by type, and as you've specified the type must be Something b the compiler will automatically use the Monad instance for Something. (I think you meant the last line to be doMagicTo var).

Upvotes: 16

rampion
rampion

Reputation: 89073

The main issue is that your definition of >>= is circular.

Haskell's do syntax is syntatic sugar for chains of >>= and >>, so your definition

m >>= f = do
    var <- m
    return $ f var         

Desugars to

m >>= f = 
    m >>= \var -> 
    return $ f var

So you're defining m >>= f as m >>= \..., which is circular.

What you need to do with >>= is define how to extract a value from m to pass to f. In addition, your f should return a monadic value, so using return is wrong here (this is closer to how you'd define fmap).

A definition of >>= for Something could be:

(Something a) >>= f = f a

This is the Identity Monad - there's a good deal written about it - it's a good starting point for understanding how monads work.

Upvotes: 8

Related Questions