简体   繁体   English

具有约束的Haskell类型同义词声明可能吗?

[英]Haskell type synonym declaration with constraint possible?

Let's say I wanna make a type synonym for all lists with Int's. 假设我想为Int的所有列表创建一个类型同义词。

I could do: 我可以:

type NumberList = [Int]

But what if I want to call all lists that contains numbers NumberList? 但是如果我想调用包含数字NumberList的所有列表呢? How would I put a constraint and say that all [a] as long as "Num a" should be called the same? 我如何设置约束并说所有[a]只要“Num a”应该被称为相同?

Edit:: After seeing the answers I rethought. 编辑::看到答案后,我重新思考。 It seemed like I was going against one of the basic ideas behind Haskell and the reward was relatively small (just a formal matter). 看起来我反对Haskell背后的一个基本思想,奖励相对较小(只是一个正式的问题)。 I decided on this: If a type has the need for two instances identical to each other that only differ in Int or Float, then the difference between them is too small to warrant the workaround needed to accomplish the use of both Int and Float but calling them the same thing, which is why I must restrict the use to one of them. 我决定这样做:如果一个类型需要两个相同的实例,只有Int或Float不同,那么它们之间的差异太小,无法保证完成Int和Float的使用所需的解决方法但是调用它们是同一个东西,这就是为什么我必须将使用限制在其中之一。 IF however there is an important reason to why I should have both, then I can probably reflect this important reason in the name of the instance and thereby avoid the problem by doing: 但是,如果有一个重要的原因,为什么我应该同时拥有两者,那么我可以在实例的名称中反映这个重要的原因,从而通过这样做避免问题:

data Thing = Thing_A(String, String Float) | Thing_B(String,String,Int)

---and thereby stick to Haskell's typing system and still accept both of them as data type Thing. ---从而坚持Haskell的打字系统,仍然接受它们作为数据类型Thing。 What I wanted to do at first was 我最初想做的是

data Thing = Thing(String, String, Float) | Thing(String, String, Int)

This corresponds to existential quantification. 这对应于存在量化。 In pseudo-Haskell, 在伪Haskell中,

type NumberList = exists a . Num a => [a]

I say «pseudo» because GHC doesn't allow introducing existential quantifiers on the fly — you need to create a separate datatype for that. 我说«伪»因为GHC不允许动态引入存在量词 - 你需要为它创建一个单独的数据类型。

Now, most of the type you'd use NumberList to the left of the arrow, where «exists» effectively changes its meaning to «forall». 现在,大多数类型你使用箭头左侧的NumberList,其中«exists»有效地将其含义改为«forall»。

That is, instead of writing 也就是说,而不是写作

isIncreasing :: NumberList -> Bool

which is the same as 这是一样的

isIncreasing :: (exists a . Num a => [a]) -> Bool

you can write 你可以写

isIncreasing :: forall a . Num a => [a] -> Bool

or simply 或者干脆

isIncreasing :: Num a => [a] -> Bool

Of course, having a type synonym seems like less code, but it has disadvantages as well. 当然,拥有类型同义词似乎代码较少,但它也有缺点。 These disadvantages, by the way, are typical for object-oriented programming, which is based on the existential approach. 顺便说一下,这些缺点是面向对象编程的典型特征,它基于存在方法。

For example, you want to concatenate two lists. 例如,您想要连接两个列表。 Ordinarily you would write 通常你会写

(++) :: forall a . [a] -> [a] -> [a]

(where again forall is unnecessary and added for clarity). (为了清楚起见,不再需要并且添加了forall )。 Since a is the same across the entire signature, that ensures that you are concatenating lists of the same type. 由于整个签名中的a是相同的,因此可确保您连接相同类型的列表。

How do we concatenate two numeric lists? 我们如何连接两个数字列表? A signature 一个签名

(++) :: NumberList -> NumberList -> NumberList

wouldn't work, since one list may contain Ints and the other may contain Doubles. 不起作用,因为一个列表可能包含Ints而另一个列表可能包含双打。 And the resulting NumberList has to contain values of a single type. 结果NumberList必须包含单个类型的值。

Or, say, you want to find the sum of the list elements. 或者,比方说,您想要找到列表元素的总和。

Usually you write 通常你写

sum :: Num a => [a] -> a

Notice that the result type is the same as the type of list elements. 请注意,结果类型与列表元素的类型相同。 Alas, we cannot do the same for NumberList! 唉,我们不能为NumberList做同样的事情!

sum :: NumberList -> ???

What is the result type? 结果类型是什么? We could apply existential quantification there as well. 我们也可以在那里应用存在量化。

sum :: NumberList -> (exists a . Num a => a)

But now the connection between the original list type and the sum type is lost — at least for Haskell's type system. 但是现在原始列表类型和总和类型之间的连接丢失了 - 至少对于Haskell的类型系统而言。 If you then decide to write a function like 如果你决定写一个像这样的函数

multiplySum :: Integer -> [Integer] -> Integer
multiplySum x ys = x * sum ys

then you'll get a type error, because sum ys may be of any type, not necessarily of type Integer. 然后你会得到一个类型错误,因为sum ys可以是任何类型,不一定是Integer类型。

It would work if you pushed everything to extreme and made every type existentially-quantified — but then you'd end up essentially with another object-oriented-like language with all their problems. 如果你将所有东西都推向极端并使每种类型都存在量化 - 那么它就会起作用 - 但是你最终会用另一种面向对象的语言来解决所有问题。

(That said, there are some good use cases for existential quantification, of course.) (也就是说,当然存在一些用于存在量化的好用例。)

Use data and parameters, not existentials to get contexts 使用数据和参数,而不是存在来获取上下文

I think if you wanted 我想如果你想要的话

data Thing = Good [(Char,Int)] | Bad String | Indifferent Leg

but also sometimes 但有时也是

data Thing = Good [(Char,Float)] | Bad String | Indifferent Arm

You could define 你可以定义

data Thing num bodypart = Good [(Char,num)] | Bad String | Indifferent bodypart

or if you want to make sure num is always numeric, you could do 或者如果你想确保num始终是数字,你可以这样做

data Num num => Thing num bodypart = Good [(Char,num)] | Bad String | Indifferent bodypart

and finally, you could restrict the types allowed in bodypart by defining your own class: 最后,您可以通过定义自己的类来限制bodypart允许的类型:

class Body a where
   -- some useful function(s) here

instance Body Leg where
   -- define useful function(s) on Legs
instance Body Arm
   -- define useful function(s) on Arms

data (Num num,Body bodypart) => Thing num bodypart = 
                                                             Good [(Char,num)] | Bad String | Indifferent bodypart

I'd like to discourage you from using existential types via the forall constructor or via GADTs because adding the num parameter to your data type is considerably more useful in practice, even though it takes more typing. 我想从通过FORALL构造或通过使用GADTs存在类型劝阻你,因为加入num参数数据类型是相当实际上更有用,即使它需要更多的输入。

Constraints on type synonyms? 类型同义词的约束?

Note that when you use a constraint like 请注意,当您使用约束时

data (Num num) => Thing num = T [(Char,num)]

really only changes the type of the constructor T to 实际上只是将构造函数T的类型更改为

T :: (Num num) => [(Char,num)] -> Thing num

instead of T :: [(Char,num)] -> Thing num . 代替T :: [(Char,num)] -> Thing num This means every time you use T , there needs to be a context (Num num) , but that's really what you wanted - to stop people putting data in your data type that isn't numeric. 这意味着每次使用T ,都需要有一个上下文(Num num) ,但这正是您想要的 - 阻止人们将数据放入非数字的数据类型中。

A consequence of this fact is that you can't write 这个事实的结果是你不能

type Num num => [(Char,num)]

because there's no data constructor function T for the context (Num num) to be required on; 因为没有数据构造函数T需要上下文(Num num) ; if I have a [('4',False)], it automatically matches the type [(Char,num)] because it's a synonym . 如果我有一个[('4',False)],它会自动匹配[(Char,num)]类型[(Char,num)]因为它是一个同义词 The compiler can't be going running around your code looking for instances before it decides what type something is. 在确定实际类型之前,编译器不能在代码中运行查找实例。 In the data case, it's got a constructor T that tells it the type, and it can guarantee there's a Num num instance because it checked your use of the function T . data情况下,它有一个构造函数T告诉它类型,它可以保证有一个Num num实例,因为它检查了你对函数T No T , no context. 没有T ,没有上下文。

GHC allows this using RankNTypes. GHC允许使用RankNTypes。

So you can do this: 所以你可以这样做:

type NumList = forall a . (Num a ,Fractional a) => [a]

And then if we have: 如果我们有:

numList:: NumList
numList = [1,2,3]

fracList:: NumList
fracList = [1.3,1.7]

doing a concatenation yields : 进行连接产生:

fracList ++ numList :: Fractional a => [a]

which NumList is a synonym of. NumList是其中的同义词。 All in all i really don't see the point in this case. 总而言之,在这种情况下,我真的不明白这一点。

It makes not much sense to have such a type when you can't recover that original type. 当你无法恢复原始类型时,拥有这样的类型没有多大意义。 If you need just certain Num s, you should simply wrap them instead of conjuring heavy magick: 如果你只需要一定数量的Num ,你应该简单地包裹它们而不是变成重型魔法:

data NumWrapper = WInt Int 
                | WDouble Double 
                | WFloat Float 
                deriving Show

numList :: [NumWrapper]
numList = [WInt 12, WFloat 1.2, WDouble 3.14]

If you really want to be open for arbitrary Num types, then a list might be just the wrong collection for you. 如果你真的想要为任意Num类型打开,那么列表可能只是你错误的集合。 There are HLists etc, see http://www.haskell.org/haskellwiki/Heterogenous_collections 有HLists等,请参阅http://www.haskell.org/haskellwiki/Heterogenous_collections

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM