6

Background: I don't understand ~ and am requesting a use case.

Given:

{-# LANGUAGE GADTs #-}

f :: a ~ b => a -> b -> b
f a b = a

g :: a -> a -> a
g a b = a

It seems to me that both functions are equal:

Prelude> :r
[1 of 1] Compiling Main             ( TypeEq.hs, interpreted )
Ok, modules loaded: Main.
*Main> f 10 20
10
*Main> g 10 20
10

Under what circumstances would it be useful to use f over g?

Community
  • 1
  • 1
Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • 4
    Indeed, there is no advantage in such case: those functions are equal. You need something like type families or GADTs to see some useful stuff, I think. Or perhaps $Data.Typeable.eqT$ can help as a nice example. – chi Apr 27 '17 at 13:13
  • 3
    If you wonder, how `~` can be used in one very helpful way, read this great blog post from Chris Done: http://chrisdone.com/posts/haskell-constraint-trick – Shersh Apr 27 '17 at 13:28

1 Answers1

10
{-# LANGUAGE TypeFamilies #-}

import GHC.Exts (IsList(..))

fizzbuzz :: (IsList l, Item l ~ Int) => l -> IO ()
fizzbuzz = go . toList
 where go [] = return ()
       go (n:m)
        | n`mod`3==0  = putStrLn "fizz" >> go m
        | n`mod`5==0  = putStrLn "buzz" >> go m
        | otherwise   = print n >> go m

Then

Prelude> fizzbuzz [1..7]
1
2
fizz
4
buzz
fizz
7
Prelude> import Data.Vector.Unboxed as UA
Prelude UA> fizzbuzz (UA.fromList[1..7] :: UA.Vector Int)
1
2
fizz
4
buzz
fizz
7

You may now object that this should better have been done with a Foldable constraint, instead of the ugly conversion to a list. Actually this couldn't be done, because unboxed vectors do not have a foldable instance due to the Unbox constraint!

It could, however, just as well have been done with a non-equational constraint, namely

fizzbuzz :: (IsList l, Num (Item l), Eq (Item l), Show (Item l))
     => l -> IO ()

That is more general, but arguably also more awkward. When you need, in practice, only one contained-type anyway, an equational constraint may be a good choice.

Indeed, I sometimes find it convenient to toss in an equational constraint just to make a type signature more concise, if it's a bit repetitive: the signature

complicatedFunction :: Long (Awkward (Type a) (Maybe String))
                 -> [Long (Awkward (Type a) (Maybe String))]
                 -> Either String (Long (Awkward (Type a) (Maybe String)))

can be replaced with

complicatedFunction :: r ~ Long (Awkward (Type a) (Maybe String))
             => r -> [r] -> Either String r

which may be better than the other DRY-possibility of

type LAwkTS a = Long (Awkward (Type a) (Maybe String))

complicatedFunction :: LAwkTS a -> [LAwkTS a] -> Either String (LAwkTS a)
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319