Haskell:将 Double 类型的函数限制为仅适用于整数

发布于 2024-08-25 01:56:28 字数 405 浏览 6 评论 0原文

假设我正在编写一个函数,该函数接受整数列表并仅返回列表中小于 5.2 的整数。我可能会做这样的事情:

belowThreshold = filter (< 5.2)

很容易,对吧?但现在出于我自己的设计原因,我想限制此函数仅适用于 [Int] 类型的输入列表。这似乎是一个合理的要求。唉,不。如下所示约束类型的声明:

belowThreshold :: [Integer] -> [Integer]
belowThreshold = filter (< 5.2)

导致类型错误。那么这里有什么故事呢?为什么执行过滤器(<5.2)似乎将我的输入列表转换为双精度?如何制作一个仅接受整数列表并仅返回整数列表的函数版本?为什么类型系统讨厌我?

Suppose I'm writing a function that takes a list of integers and returns only those integers in the list that are less than 5.2. I might do something like this:

belowThreshold = filter (< 5.2)

Easy enough, right? But now I want to constrain this function to only work with input lists of type [Int] for design reasons of my own. This seems like a reasonable request. Alas, no. A declaration that constraints the types as so:

belowThreshold :: [Integer] -> [Integer]
belowThreshold = filter (< 5.2)

Causes a type error. So what's the story here? Why does doing filter (< 5.2) seem to convert my input list into Doubles? How can I make a version of this function that only accepts integer lists and only returns integer lists? Why does the type system hate me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

暗藏城府 2024-09-01 01:56:28

在添加注释之前检查 ghci 中的 BelowThreshold 的推断类型:

> :t belowThreshold
belowThreshold :: [Double] -> [Double]

听起来像您期望的 Num a =>; [一]-> [a] 当你说“约束这个函数”时。当您添加 [Integer] -> 时,您实际上正在更改函数的类型。 [Integer] 注释。

要实现此目的,请使用显式转换:

belowThreshold = filter ((< 5.2) . fromIntegral)

现在 belowThreshold :: [Integer] -> [Integer] 就像你想要的那样。但在与 5.2 比较之前,整数会转换为双精度数。

那么为什么需要转换呢?类型错误可能会误导您:与 5.2 相比,整数列表没有转换为双精度数,真正的问题是只有双精度数可以与双精度数进行比较,因此您必须< /em> 将 Double 列表传递给 belowThreshold。 Haskell 没有隐式转换,甚至数字之间也没有。如果你想要转换,你必须自己编写它们。

出于我自己的设计原因,我想限制此函数仅适用于 [Int] 类型的输入列表。这似乎是一个合理的要求。

嗯,从类型系统的角度来看,不是。这是合理的代码吗?

'c' < "foo"

这又如何呢?

12 < "bar"

所有这些值都是 Ord 的实例,但不能将它们与 (<) 一起使用。 Haskell 没有隐式转换。因此,即使两个值都是 NumOrd 的实例,您也无法将它们与 (<) 进行比较如果它们属于不同类型。

Check the inferred type of belowThreshold in ghci before adding your annoatation:

> :t belowThreshold
belowThreshold :: [Double] -> [Double]

It sounds like you expected Num a => [a] -> [a] when you said "constrain this function". You are actually changing the type of the function when you add the [Integer] -> [Integer] annotation.

To make this work, use an explicit conversion:

belowThreshold = filter ((< 5.2) . fromIntegral)

Now belowThreshold :: [Integer] -> [Integer] like you wanted. But the integers are converted to doubles before comparison to 5.2.

So why do you need the conversion? The type error probably misled you: the list of Integers wasn't being converted to Doubles by comparison to 5.2, the real problem is that only Doubles can be compared to Doubles, so you must pass a list of Doubles to belowThreshold. Haskell has no implicit conversions, not even between numbers. If you want conversions, you have to write them yourself.

I want to constrain this function to only work with input lists of type [Int] for design reasons of my own. This seems like a reasonable request.

Well, from the perspective of the type system, no. Is this reasonable code?

'c' < "foo"

What about this?

12 < "bar"

All of these values are instances of Ord, but you can't use them together with (<). Haskell has no implicit conversions. So even if two values are both instances of Num as well as Ord, you won't be able to compare them with (<) if they are of different types.

中性美 2024-09-01 01:56:28

您正在尝试将整数与双精度数 (5.2) 进行比较。哈斯克尔不喜欢这样。尝试使用

filter (< 6)

You are trying to compare an Integer to a double (5.2). Haskell doesn't like that. Try using

filter (< 6)
绝對不後悔。 2024-09-01 01:56:28

如果你必须使用双精度(假设它是一个参数),我会使用 ceiling

filter (< (ceiling 5.2))

现在,如果你想要一个函数将边界值作为“任何”(相关)数值,您可以创建自己的类型类来为您设置上限。

class Ceilingable a where
  ceil :: (Integral b) => a -> b

instance (RealFrac a) => Ceilingable a where
  ceil = ceiling

instance (Integral a) => Ceilingable a where
  ceil = fromIntegral

belowThreshold :: (Ceilingable a) => a -> [Integer] -> [Integer]
belowThreshold threshold = filter (< ceil threshold)

If you must use a double (let's say it is an argument), I would use ceiling:

filter (< (ceiling 5.2))

Now if you want a function that takes in the bounding value as 'any' (relevant) numeric value, you can make your own type class to ceiling the number for you.

class Ceilingable a where
  ceil :: (Integral b) => a -> b

instance (RealFrac a) => Ceilingable a where
  ceil = ceiling

instance (Integral a) => Ceilingable a where
  ceil = fromIntegral

belowThreshold :: (Ceilingable a) => a -> [Integer] -> [Integer]
belowThreshold threshold = filter (< ceil threshold)
相对绾红妆 2024-09-01 01:56:28

语法 5.2 对于任何 Fractional 都有效。 Int 不是 Fractional 的实例,也不可能也不应该是。由于将任意 Rational 转换为 Int 时要做什么尚未明确。

然而,从任意分数到 Double 的转换非常有意义(在类型的范围内)。

您的期望是由许多语言中存在的隐式强制转换驱动的。

然而,这些都是有代价的。您必须手动确保整个强制系统是汇合的。 Haskell 没有这样做,而是选择让数字文字语法利用类型系统。要在它们之间进行转换,您需要使用 fromIntegral 来明确强制转换的需要,这可以避免依赖汇合并允许程序员定义新的数字类型。

belowThreshold = filter (\x -> fromIntegral x < 5.2) 

这类似于在 C++ 中使用显式转换,例如 ((double)x < 5.2)。不过,此语句仅在默认情况下才有效,因为 5.2 可以用作 any Fractional 的成员,并且 'fromIntegral x ' 是任何 Num,它是 Fractional 的超类,因此 fromIntegral x 5.2 未指定,它只知道需要比较相同类型的两个 Fractional 值,并基于 ' 选择 Double 作为合理的默认值默认'声明。

另请注意,Int 不是唯一的 Integral 类型,因此上述方法适用于任何 Integral 值列表:

belowThreshold :: Integral a => [a] -> [a]

The syntax 5.2 is valid for any Fractional. Int is not an instance of Fractional, nor can or should it be. As what to do when converting an arbitrary Rational to an Int is underspecified.

The conversion to a Double from an arbitrary fraction, however makes perfectly good sense (within the range of the type).

Your expectation is driven by the presence of implicit coercions in many languages.

However, those come with a cost. You have to manually ensure that the entire system of coercions is confluent. Haskell does not do this, choosing instead to let numeric literal syntax leverage the type system. To convert between them you need to use fromIntegral to make explicit the need for coercion, this avoids relying on confluence and allows programmers to define new numeric types.

belowThreshold = filter (\x -> fromIntegral x < 5.2) 

This is analogous to using an explicit conversion in C++, like ((double)x < 5.2). Although, this statement only works because of defaulting, because 5.2 could be used as a member of any Fractional, and the result of 'fromIntegral x' is any Num, a superclass of Fractional, so fromIntegral x < 5.2 is underspecified, it merely knows that it needs to compare two Fractional values of the same type and it chooses Double as a reasonable default, based on a 'default' statement.

Also note that Int is not the only Integral type, so the above method works on any list of Integral values:

belowThreshold :: Integral a => [a] -> [a]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文