3
votes

While studying polyvariadic functions in Haskell I stumbled across the following SO questions:

How to create a polyvariadic haskell function?

Haskell, polyvariadic function and type inference

and thought I will give it a try by implementing a function which takes a variable number of strings and concatenates/merges them into a single string:

{-# LANGUAGE FlexibleInstances #-}

class MergeStrings r where
    merge :: String -> r
instance MergeStrings String where
    merge = id
instance (MergeStrings r) => MergeStrings (String -> r) where
    merge acc = merge . (acc ++)

This works so far if I call merge with at least one string argument and if I provide the final type.

foo :: String
foo = merge "a" "b" "c"

Omitting the final type results in an error, i.e., compiling the following

bar = merge "a" "b" "c"

results in

test.hs:12:7: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘merge’
      prevents the constraint ‘(MergeStrings t0)’ from being solved.
      Relevant bindings include bar :: t0 (bound at test.hs:12:1)
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance MergeStrings r => MergeStrings (String -> r)
          -- Defined at test.hs:6:10
        instance MergeStrings String -- Defined at test.hs:4:10
    • In the expression: merge "a" "b" "c"
      In an equation for ‘bar’: bar = merge "a" "b" "c"
   |
12 | bar = merge "a" "b" "c"
   |

The error message makes perfect sense since I could easily come up with, for example

bar :: String -> String
bar = merge "a" "b" "c"

baz = bar "d"

rendering bar not into a single string but into a function which takes and returns one string.

Is there a way to tell Haskell that the result type must be of type String? For example, Text.Printf.printf "hello world" evaluates to type String without explicitly defining.

2
To clarify: you seem to be saying that, when the type of merge a b c etc… cannot be inferred, you want GHC to infer a type of String, otherwise use the inferred type. Is this interpretation correct? I can’t think of any way to do this, but I wouldn’t be able to conclusively say that it’s impossible either.bradrn
By the way: it looks like your printf example only works because PrintfType has an instance for IO; when testing in GHCi, any type which can be specialised to IO will automatically be run. If I define instance (a ~ ()) => MergeStrings (IO a) where merge = putStrLn, bar = merge "a" "b" "c" works for me as well in GHCi. And, outside GHCi, printf "hello world" results in an ambiguous type variable error, in exactly the same way that bar = merge "a" "b" "c" does.bradrn
For clarification: I didn't want GHC to infer the result type but rather define the result type at the definition side of merge such that when using merge I do not have to provide the result type. Your observations regarding GHCi and IO are really interesting I will play around with them, too! Seems like @max-taldykin nailed itMax Maier

2 Answers

3
votes

printf works without type annotation because of type defaulting in GHCi. The same mechanism that allows you to eval show $ 1 + 2 without specifying concrete types.

GHCi tries to evaluate expressions of type IO a, so you just need to add appropriate instance for MergeStrings:

instance (a ~ ()) => MergeStrings (IO a) where
    merge = putStrLn
2
votes

Brad (in a comment) and Max are not wrong saying that the defaulting of printf "…" … to IO ( ) is the reason for it working in ghci without type annotations. But it is not the end of the story. There are things we can do to make your definition of bar work.

First, I should mention the «monomorphism restriction» — an obscure and unintuitive type inference rule we have in Haskell. For whatever reason, the designers of Haskell decided that a top level definition without a type signature should have no polymorphic variables in its inferred type — that is, be monomorphic. bar is polymorphic, so you can see that it would be affected.

Some type classes (particularly numbers) have defaulting rules that allow you to say x = 13 without a type signature and have it inferred that x :: Integer — or whatever other type you set as default. Type defaulting is only available for a few blessed classes, so you cannot have it for your own class, and without a designated default GHC cannot decide what particular monomorphic type to choose.

But you can do other things, beside defaulting, to make the type checker happy — either:

Now bar is polymorphic and works as you would expect. See:

λ putStrLn bar
abc
λ putStrLn (bar "x")
abcx
λ putStrLn (bar "x" "y")
abcxy

You can also use defaulting to make expressions such as show bar work. Since Show is among the classes that you can default when extended default rules are enabled, you can issue default (String) in the module where you want to use show bar and it will work as you would expect.