如何派生Haskell记录字段的类型?

时间:2017-09-03 10:44:12

标签: haskell monads

来自OOP这对我来说似乎是外星代码。

我不明白为什么runIdentity的类型是函数:

runIdentity :: Identity a -> a?我指定为runIdentity :: a

newtype Identity a = Identity {runIdentity :: a} deriving Show

instance Monad Identity where
  return = Identity
  Identity x >>= k = k x

instance Functor Identity where
  fmap  f (Identity x) = Identity (f x)

instance Applicative Identity where
  pure = Identity
  Identity f <*> Identity v = Identity (f v)

wrapNsucc :: Integer -> Identity Integer
wrapNsucc = Identity . succ

致电runIdentity

  

runIdentity $ wrapNsucc 5 - 输出6作为输出

3 个答案:

答案 0 :(得分:10)

runIdentity是一个简单的a字段,你是对的。但runIdentity类型Identity a -> a,因为runIdentity是从Identity a中提取该字段的函数。毕竟,在没有提供值的情况下,你无法从值中获取runIdentity

编辑: 要在评论中稍微扩展一下OOP类比,请考虑一个类

class Identity<T> {
    public T runIdentity;
}

这是Identity monad,松散地翻译成OOP代码。模板参数T基本上是您的a;因此,runIdentity的类型为T。要从对象中获取T,您可能会执行类似

的操作
Identity<int> foo = new Identity<int>();
int x = foo.runIdentity;

您认为runIdentity类型为T,但事实并非如此。你不能只做

int x = runIdentity; // Nope!

因为 - 从哪里获取runIdentity?相反,想到这就像做

Identity<int> foo = new Identity<int>();
int x = runIdentity(foo);

这显示了当你打电话给会员时实际发生的事情;你有一个函数(你的runIdentity)并为它提供一个使用的对象 - IIRC这就是Python对def func(self)的作用。因此,T实际上是以runIdentity为参数来返回Identity<T>,而不是明确地为T类型。

因此,它的类型为Identity a -> a

答案 1 :(得分:4)

另一种看待这种情况的方法是,Haskell中的记录语法基本上只是代数数据类型的语法糖,即记录并不真正存在于Haskell中,只有代数数据类型才能存在,可能还有一些额外的句法细节。因此,成员的概念与类在许多OO语言中的表达方式相同。

data MyRecord = MyRecord { myInt :: Int, myString :: String }

真的只是

data MyRecord Int String

附加功能

myInt :: MyRecord -> Int
myInt (MyRecord x _) = x

myString :: MyRecord -> String
myString (MyRecord _ y) = y

自动定义。

你自己用普通代数数据类型记录语法的唯一事情就是给你一个很好的方法来制作一个MyRecord的副本,它只改变了一个字段的子集,并提供了一种很好的命名方式某些模式。

copyWithNewInt :: Int -> MyRecord -> MyRecord
copyWithNewInt x r = r { myInt = x }

-- Same thing as myInt, just written differently
extractInt :: MyRecord -> Int
extractInt (MyRecord { myInt = x }) = x

因为这只是普通代数数据类型的语法糖,所以你总是可以回到常规方式做事。

-- This is a more verbose but also valid way of doing things
copyWithNewInt :: Int -> MyRecord -> MyRecord
copyWithNewInt x (MyRecord _ oldString) = MyRecord x oldString

顺便说一下,这就是为什么存在一些其他荒谬的看似约束的原因(最突出的是你可以再次使用myInt的记录语法定义另一种类型,否则你将创建两个函数在具有相同名称的相同范围内,Haskell不允许)。

因此

newtype Identity a = Identity {runIdentity :: a} deriving Show

是等效的(减去方便的更新语法,当你只有一个字段时,它并不重要)

newtype Identity a = Identity a deriving Show

runIdentity :: Identity a -> a
runIdentity (Identity x) = x

使用记录语法只需将所有内容压缩成一行(并且可能更深入地了解runIdentity被命名的原因,即作为动词,而不是名词。)

答案 2 :(得分:2)

newtype Identity a = Identity {runIdentity :: a} deriving Show

在此使用记录语法,您真正创建了两个名为runIdentity的内容。

一个是构造函数Identity的字段。您可以将其与记录模式语法一起使用,如case i of Identity { x = runIdentity } -> x中所示,其中匹配值i :: Identity a以将字段的内容提取到局部变量x中。您还可以使用记录构造或更新语法,如Identity { runIdentity = "foo" }i { runIdentity = "bar" }

在所有这些案例中,runIdentity本身并不是一个独立的事物。您只是将其用作更大的句法结构的一部分,才能说明您正在访问的Identity字段。 &#34;插槽&#34;在Identify a字段的帮助下引用的runIdentity确实存储了a类型的内容。但是,此runIdentity字段类型为a的值。它根本不是一个真正的价值,因为它需要具有关于引用特定&#34;槽&#34;的这些额外属性(值没有)。在数据类型中。价值观是独立的东西,存在并且自己有意义。字段不是;字段内容,这就是我们使用类型来对字段进行分类的原因,但字段本身不是值。 1 值可以放在数据结构中,从函数返回等。无法定义可以放在数据结构中的值,退出,然后使用记录模式,构造或更新语法。

使用记录匹配语法定义的另一个名为runIdentity的是普通函数。函数值;您可以将它们传递给其他函数,将它们放在数据结构中等。目的是为您提供帮助,以获取类型为Identity a的值的字段值。但是因为你必须指定哪个 Identity a值来获取runIdentity字段的值,你必须将Identity a传递给函数。因此,runIdentity 函数Identity a -> a类型的值,与runIdentity 字段不同,后者是非值描述的按类型a

查看此区别的一种简单方法是在文件中添加myRunIdentity = runIdentity之类的定义。该定义声明myRunIdentity等于runIdentity,但您只能定义。确定myRunIdentity将是Identity a -> a类型的函数,您可以将其应用于Identity a类型的内容以获得a值。但它不能用于记录语法作为字段。字段runIdentity没有出现&#34;该定义中的值runIdentity

这个问题可能是由:t runIdentity类型提示到ghci,要求它显示类型。它会回答runIdentity :: Identity a -> a。原因是:t语法适用于 2 。您可以在那里键入任何表达式,它将为您提供将产生的值的类型。因此:t runIdentity看到了runIdentity (函数),而不是runIdentity字段。

作为最后一点,我一直在讨论字段runIdentity :: a和函数runIdentity :: Identity -> a是如何分开的两个方面。我这样做是因为我认为将两者完全分开会让人们感到困惑,为什么会有两个不同的答案来解决runIdentity&#34;的类型。但它也是一个完全有效的解释,说runIdentity是一个单一的东西,而且当你使用一个字段作为一等值时,它就像一个函数一样。这就是人们经常谈论领域的方式。所以,如果其他消息来源坚持认为只有一件事,请不要感到困惑。这些只是两种不同的方式来看待相同的语言概念。

1 透镜的一个视角,如果您已经听过它们,那就是它们是普通的值,可以用来给我们提供我们需要的所有语义。字段&#34;,没有任何特殊用途的语法。因此,理论上,假设语言根本不能提供任何字段访问语法,只需在我们声明新数据类型时为我们提供镜头,并且我们能够做到。

但是Haskell记录语法字段不是镜头;作为价值观他们只是&#34; getter&#34;函数,这就是为什么有专门的模式匹配,构造和更新语法,以便以超出普通值的方式使用字段。

2 好吧,更恰当的是它适用于表达式,因为它对代码进行类型检查,不运行代码然后查看值以查看它是什么类型(无论如何都不会工作,因为运行时Haskell值在GHC系统中没有任何类型信息。但是你可以模糊线条并将值和表达式称为同类事物;领域完全不同。