Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell instance of `bind` for a custom type

I'm trying to create an instance for bind operator (>>=) to the custom type ST a

I found this way to do it but I don't like that hardcoded 0.

Is there any way to implement it without having the hardcoded 0 and respecting the type of the function?

newtype ST a = S (Int -> (a, Int))
    
-- This may be useful to implement ">>=" (bind), but it is not mandatory to use it
runState :: ST a -> Int -> (a, Int)
runState (S s) = s
        
instance Monad ST where
      return :: a -> ST a
      return x = S (\n -> (x, n))
       
      (>>=) :: ST a -> (a -> ST b) -> ST b
      s >>= f = f (fst (runState s 0))
like image 383
Nibblex Avatar asked Apr 06 '21 23:04

Nibblex


People also ask

What does bind do in Haskell?

Bind takes a non-composable function f and returns a new function g that accepts the monadic type as input and returns the monadic type. g is composable. The unit function takes an argument of the type that f expected, and wraps it in the monadic type.

How does the io monad work?

The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.

How does Haskell deal with side effects?

Haskell is a pure language Moreover, Haskell functions can't have side effects, which means that they can't effect any changes to the "real world", like changing files, writing to the screen, printing, sending data over the network, and so on.

How is io pure in Haskell?

Calculations involving such operations cannot be independent - they could mutate arbitrary data of another computation. The point is - Haskell is always pure, IO doesn't change this. So, our impure, non-independent codes have to get a common dependency - we have to pass a RealWorld .

How to create a custom data type in Haskell?

The easiest way to create a custom data type in Haskell is to use the data keyword: The name of the type is specified between data and =, and is called a type constructor.

What are the problems with Haskell-style overloading?

A problem inherent with Haskell-style overloading is the possibility of an ambiguous type. For example, using the readand showfunctions defined in Chapter 11, and supposing that just Intand Boolare members of Readand Show, then the expression   let x = read "..."  in show x  -- invalid is ambiguous, because the types for showand read, show

Is [a]is an instance of foounder in Haskell?

This example is valid Haskell. Since Foois a superclass of Bar, the second instance declaration is only valid if [a]is an instance of Foounder the assumption Num a. The first instance declaration does indeed say that [a]is an instance of Foounder this assumption, because Eqand Showare superclasses of Num.

Can We express monomorphic type variables in Haskell?

It is worth noting that the explicit type signatures provided by Haskell are not powerful enough to express types that include monomorphic type variables. For example, we cannot write   f x = let             g :: a -> b -> ([a],b)             g y z = ([x,y], z)           in ...


Video Answer


2 Answers

I often find it easier to follow such code with a certain type of a pseudocode rewrite, like this: starting with the

instance Monad ST where
      return :: a -> ST a
      return x = S (\n -> (x, n))

we get to the

  runState (return x) n = (x, n)

which expresses the same thing exactly. It is now a kind of a definition through an interaction law that it must follow. This allows me to ignore the "noise"/wrapping around the essential stuff.

Similarly, then, we have

      (>>=) :: ST a -> (a -> ST b) -> ST b
      s >>= f = -- f (fst (runState s 0))   -- nah, 0? what's that?
      -- 
      -- runState (s >>= f) n = runState (f a) i where 
      --                                   (a, i) = runState s n
      --
                S $       \ n ->       let (a, i) = runState s n in
                                runState (f a) i

because now we have an Int in sight (i.e. in scope), n, that will get provided to us when the combined computation s >>= f will "run". I mean, when it will runState.

Of course nothing actually runs until called upon from main. But it can be a helpful metaphor to hold in mind.

The way we've defined it is both the easiest and the most general, which is usually the way to go. There are more ways to make the types fit though.

One is to use n twice, in the input to the second runState as well, but this will leave the i hanging unused.

Another way is to flip the time arrow around w.r.t. the state passing, with

                S $       \ n ->       let (a, i2) = runState s i 
                                           (b, i ) = runState (f a) n
                                        in (b, i2)

which is a bit weird to say the least. s still runs first (as expected for the s >>= f combination) to produce the value a from which f creates the second computation stage, but the state is being passed around in the opposite direction.

like image 160
Will Ness Avatar answered Nov 15 '22 08:11

Will Ness


The most important thing to keep in mind is that your ST type is a wrapper around a function. What if you started your definition as (>>=) = \s -> \f -> S (\n -> ... )? It might be (ok, is) a bit silly to write separate lambdas for the s and f parameters there, but I did it to show that they're not really any different from the n parameter. You can use it in your definition of (>>=).

like image 22
Carl Avatar answered Nov 15 '22 07:11

Carl