1

One fundamental of Haskell is that a string is really a list of characters. Another fundamental is referential transparency, in that a pure function must always give the exact same result when given the same input.

However, these do not seem to be true in actual practice. For example, I created this function:

stringCharList :: String -> [Char] -> Bool
stringCharList a b
    | a==b = True
    | otherwise = False

if I enter (in the repl)

>stringCharList "hello" ['h','e','l','l','o']

I get back True, as expected. However, if I build this function, I get an error (shown below)

vowelz :: Char -> Bool
vowelz a 
    | a `elem` vowels = True
    | otherwise = False
      where vowels = "aeiou"
JWB2.hs:38:22: error:
    • Ambiguous type variable ‘t0’ arising from the literal ‘"aeiou"’
      prevents the constraint ‘(Data.String.IsString
                                  (t0 Char))’ from being solved.
      Relevant bindings include
        vowels :: t0 Char (bound at JWB2.hs:38:13)
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance (a ~ Char) => Data.String.IsString [a]
          -- Defined in ‘Data.String’
        ...plus two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: "aeiou"
      In an equation for ‘vowels’: vowels = "aeiou"
      In an equation for ‘vowelz’:
          vowelz a
            | a `elem` vowels = True
            | otherwise = False
            where
                vowels = "aeiou"
   |
38 |       where vowels = "aeiou"
   |                      ^^^^^^^

Either of these two alternatives resolves the error

vowelz :: Char -> Bool
vowelz a
    | a `elem` vowels = True
    | otherwise = False
      where vowels = "aeiou" :: String

-- OR.....

vowelz :: Char -> Bool
vowelz a
    | a `elem` vowels = True
    | otherwise = False
      where vowels = ['a','e','i','o','u']

I have tracked this down to the declaration of overloaded strings at the top of my experimental/learning hs file:

{-# LANGUAGE OverloadedStrings #-}

if this line is present, I observe the issues described above. If the line is removed, the issues go away and the observed behavior is as expected.

It seems to me that the OverloadedStrings declaration breaks both those fundamentals I listed at the start, which makes it hard to understand how Haskell can be considered "pure". It also suggests that if I want to make modular code that can be incorporated into other projects, I must account for the various hacks and kludges (and perhaps future, not-yet-invented hacks and kludges) or my pure function might break.

Can anyone help me wrap my brain around this?

1 Answer 1

2

Somewhere in the Haskell based libraries String is defined as a type alias as:

type String = [Char]

The problem with your function is that GHC is not able to determine the type of "aeiou". In this case you would need to add a type signature like this (untested but should work):

vowelz :: Char -> Bool
vowelz a
    | a `elem` vowels = True
    | otherwise = False
   where
     vowels :: String
     vowels = ['a','e','i','o','u']
4
  • 1
    Your solution is basically what I included in my question. The reason GHC is not able to determine the type is because of the OverloadedStrings language declaration. Without the OverloadedStrings and also without your solution, the code compiles just fine. My concern is around being forced to account for any arbitrary language modification when trying to write reusable code. My attitude is "if it compiles in a bare bones configuration, it should 'just work' in any configuration." Perhaps that is unrealistic, but then Haskell should not tout referential transparency.
    – XiTouch
    Feb 13, 2022 at 20:39
  • 2
    The difference is that my solution includes a type signature for vowels which GHC can use to resolve uncertainty in the type of that value. This has nothing to do with referential transparency and everything to do with automated type inference. Feb 13, 2022 at 23:12
  • It sounds like the "best practice" is to not rely on type inference, and also to not assume that successfully compiled code is good enough. Thanks.
    – XiTouch
    Feb 14, 2022 at 8:11
  • As someone who has been doing Haskell professionally I would say best practice is * All top level function should have type signatures (compiler checked documentation). * Other type signatures when compiler needs it or for documenation. If code compiles (ie GHC can figure out the types) it is highly reliable. Feb 14, 2022 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.