我觉得学习haskell的一个思维障碍是data
有时将函数定义为数据。
data Person = Person {
name :: String,
age :: Int
}
这很直观,类似于其他语言。但是在
newtype StateT s m a = StateT {
runStateT :: s -> m (a,s)
}
这基本上是调用函数s->m (a,s)
“data”
我很容易理解,在高阶函数中,“函数”确实作为数据传递。但是在类型定义中,使用函数来定义类型,这是非常令人惊讶的。
所以我的问题是:这会给Haskell类型系统带来表现力吗?这一切背后的理论是什么?
答案 0 :(得分:7)
它只是一个围绕函数的包装器。
foo :: String -> [(Int, String)]
foo xs = zip [1..] (map pure xs)
fooState :: StateT String [] Int
fooState = StateT foo
数据构造函数StateT
采用单个参数,类型为s -> m (a, s)
的函数,并返回类型StateT s m a
的值。在这里,我们有
s ~ String
m ~ []
a ~ Int
由于声明类型为foo
。
与Python之类的语言中的函数引用真的没什么不同。 (请注意,foo
的类型与Haskell示例略有不同,但将引用传递给foo
到StateT.__init__
的想法是重要的注意)。
class StateT:
def __init__(self, f):
self.runStateT = f
def foo(xs):
return enumerate(xs)
x = StateT(foo)
答案 1 :(得分:2)
与任何其他值一样,函数具有类型,并且如您所说,可以作为参数传递。数据类型字段也可以存储函数,这与其他值的使用方式没有区别:
GHCi> :t ("foo", "bar")
("foo", "bar") :: ([Char], [Char])
GHCi> :t (reverse, drop 2)
(reverse, drop 2) :: ([a1] -> [a1], [a2] -> [a2])
从这个角度来看,......之间没有本质区别。
newtype MyInt = MyInt { getMyInt :: Int }
......和:
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
这会给Haskell类型系统带来表现力吗?
以下是两种方式。首先,函数类型的包装器和类型同义词允许编写较少杂乱的类型签名。这个,例如......
withStateT :: (s -> s) -> StateT s m a -> StateT s m a
......读起来比以前好多了:
withStateT :: (s -> s) -> (s -> m (a, s)) -> (s -> n (a, s))
其次,newtype包装器使得为函数类型编写类实例变得可行 - 没有它们,例如,我们不会拥有StateT
具有的重要实例(Functor
, Applicative
,Monad
,MonadTrans
等。)