简体   繁体   中英

Haskell: Functions in list comprehensions

I am teaching myself Haskell, and playing with list comprehensions. I wrote this list comprehension:

[ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c]

I hoped it would produce the list of values c where c is an integer (c == round c), which would be only 5, but it does not compile. Playing around some more, I've found I can't really embed any functions in a list comprehension, I'm sure there is a way, I just don't know how.

Here's the error code:

<interactive>:158:1: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘it’
      prevents the constraint ‘(Floating t0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance Floating Double -- Defined in ‘GHC.Float’
        instance Floating Float -- Defined in ‘GHC.Float’
    • In the first argument of ‘print’, namely ‘it’
      In a stmt of an interactive GHCi command: print it

Thanks!

First of all, include necessary definitions in a question like this.

hyp :: Floating a => a -> a -> a
hyp a b = sqrt $ a^2 + b^2

Now. You can “embed” functions all right in list comprehensions . Apparently you just chose some unfortunate ones! round has the following type:

GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
Prelude> :t round
round :: (Integral b, RealFrac a) => a -> b

So, for round c == c to make sense, you'd need to have a number type that's both an instance of Integral and of RealFrac . In other words, this type would contain fractions, yet all its elements would be integers . Well, you can't have your cake and eat it too!

This problem would have been much more evident, like so often in Haskell, if you had actually written out some type signatures. While fooling around like that, it often helps to just select some simple example type. Perhaps the most reasonable thing would seem:

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c] :: [Integer]
<interactive>:6:41:
    No instance for (Floating Integer) arising from a use of ‘hyp’
    In the expression: hyp a b
    In an equation for ‘c’: c = hyp a b
    In the expression:
        [c |
           a <- [1 .. 3], b <- [1 .. 4], let c = hyp a b, c == round c] ::
          [Integer]

Ok, so Integer doesn't work because you're trying to do real arithmetic, with those square roots in hyp . This is not possible with Integer , you need a Floating type like Double . Let's try that:

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c] :: [Double]

<interactive>:8:55:
    No instance for (Integral Double) arising from a use of ‘round’
    In the second argument of ‘(==)’, namely ‘round c’
    In the expression: c == round c
    In a stmt of a list comprehension: c == round c

Ok, this is because as I said, round always gives integral-type results. However, you can always convert such an integral type to a Double again:

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == fromIntegral (round c)] :: [Double]
[5.0]

Note that this is not really a good solution though: you don't really want the result to be floating point if you go already check that the elements are really integral! I'd recommend in this case not just evaluating hyp as such at all. Better use this comprehension:

Prelude> [ c | a <- [1..3], b <- [1..4], let c² = a^2 + b^2; c = round . sqrt $ fromIntegral c², c^2==c²] :: [Integer]
[5]

One big argument for this version is that it does the comparison in Integer , not in Double . Floating-point equality comparison is something you should best stay away from entirely if you can help it; in this case it's mostly harmless because the interesting thing is the integer subset, which can in fact be represented exactly (unlike decimal fractions like 0.1 ). Still you ca get wrong results this way: in particular, for sufficiently large floating-point numbers, c == fromInteger (round c) will always be true, because above a certain threshold all values are integral after all.

Prelude> [ c | a <- take 4 [1000000000000..], b <- take 5 [1000000000000..], let c = hyp a b, c == fromIntegral (round c)] :: [Float]
[1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12]

But none of these are actually correct integral hypothenuses, as you can see with the version that does the comparison in Integer :

Prelude> [ c | a <- take 4 [1000000000000..], b <- take 5 [1000000000000..], let c² = a^2 + b^2; c = round . sqrt $ fromIntegral c², c^2==c²] :: [Integer]
[]

Strictly speaking, this improved version is also not safe though – it doesn't give false positives, but may not find actual Pythagorean triples because the lossy floating-point steps can already destroy equality. To do it real properly, you need an all-integral

intSqrt :: Integral a => a -> Maybe a

This could probably done quite efficiently by taking the float sqrt as a starting value for a few rounds of pseudo Newton-Raphson in integer arithmetic.


In principle, the round function could also have a more relaxed signature.

round' :: (Num b, RealFrac a) => a -> b
round' = fromInteger . round

With that version, your original code would work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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