1

I feel one of the mind hurdles in learning haskell is that data sometimes defines functions as data.

data Person = Person { name :: String, age :: Int } 

This is intuitive and resembles other languages. But in

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) } 

This is basically calling a function s->m (a,s) "data"

I can readily understand that in higher order functions, "functions" are indeed passed around as data. But in type definitions, using functions to define types, that's quite surprising.

So my question is: will this bring expressiveness to Haskell type system? What is the theory behind all this?

1
  • 5
    Wel functions are in fact data. But I do not really understand your question I'm afraid. Commented Mar 5, 2018 at 18:27

2 Answers 2

8

It's just a wrapper around a function.

foo :: String -> [(Int, String)] foo xs = zip [1..] (map pure xs) fooState :: StateT String [] Int fooState = StateT foo 

The data constructor StateT takes a single argument, a function of type s -> m (a, s), and returns a value of type StateT s m a. Here, we have

  • s ~ String
  • m ~ []
  • a ~ Int

due to the declare type of foo.


It's really not very different from a function reference in a language like Python. (Note that foo has a slightly different type here than in the Haskell example, but the idea of passing a reference to foo to StateT.__init__ is the important thing to note).

class StateT: def __init__(self, f): self.runStateT = f def foo(xs): return enumerate(xs) x = StateT(foo) 
Sign up to request clarification or add additional context in comments.

2 Comments

In Python, the function is clearly a behavior of the data. But in Haskell, the function seems more mixed up with the data type itself.
Not really; StateT really is just a wrapper around a function. runStateT (StateT foo) "bar" == foo "bar". Since Haskell is statically typed, you can (and must) be specific about exactly which types of functions you can wrap, whereas Python doesn't even enforce that the argument to StateT.__init__ is a function, regardless of the intention for it to do so. (Although you can provide a type hint for static analysis, even if it is ignored at run time.)
3

Functions, like any other value, have types and, as you say, can be passed as arguments. Fields of data types can also store functions, which again is no different from how other values can be used:

GHCi> :t ("foo", "bar") ("foo", "bar") :: ([Char], [Char]) GHCi> :t (reverse, drop 2) (reverse, drop 2) :: ([a1] -> [a1], [a2] -> [a2]) 

From this point of view, there is no essential difference between...

newtype MyInt = MyInt { getMyInt :: Int } 

... and:

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) } 

will this bring expressiveness to Haskell type system?

Here are two of the ways in which it does so. Firstly, wrappers and type synonyms for function types allow writing less cluttered type signatures. This, for example...

withStateT :: (s -> s) -> StateT s m a -> StateT s m a 

... reads quite a bit nicer than:

withStateT :: (s -> s) -> (s -> m (a, s)) -> (s -> n (a, s)) 

Secondly, newtype wrappers make it feasible to write class instances for function types -- without them, for instance, we wouldn't have the crucially important instances that StateT has (Functor, Applicative, Monad, MonadTrans, etc.).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.