Reputation: 1045
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.
Upvotes: 3
Views: 347
Reputation: 4832
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:
bar :: MergeStrings r => r
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.
Upvotes: 2
Reputation: 12908
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
Upvotes: 3