I was experimenting with phantom types in Haskell. My goal is to convert the LangCode
Type to it's corresponding Phantom Type representation via Type Classes for example DE
to Lang DE
.
module Main (main) where
import Data.Proxy (Proxy(..))
data DE
data EN
data LangCode
= DE
| EN
deriving (Eq, Show)
type Lang a = Proxy a
de :: Lang DE
de = Proxy
en :: Lang EN
en = Proxy
class ToLangCode a where
toLangCode :: Lang a -> LangCode
instance ToLangCode DE where
toLangCode _ = DE
instance ToLangCode EN where
toLangCode _ = EN
class FromLangCode a where
fromLangCode :: LangCode -> Lang a
instance FromLangCode DE where
fromLangCode DE = Proxy
instance FromLangCode EN where
fromLangCode EN = Proxy
main :: IO ()
main = do
print $ de -- Output => Proxy
print $ en -- Output => Proxy
print $ toLangCode de -- Output => DE
print $ toLangCode en -- Output => EN
-- works
print $ (fromLangCode DE :: Lang DE) -- Output => Proxy
print $ (fromLangCode EN :: Lang EN) -- Output => Proxy
-- throws an error
print $ fromLangCode DE -- Output => Proxy
print $ fromLangCode EN -- Output => Proxy
With a type annotation it works fine. But without it I get this error.
[1 of 1] Compiling Main ( main.hs, main.o )
main.hs:50:11: error:
* Ambiguous type variable `a0' arising from a use of `fromLangCode'
prevents the constraint `(FromLangCode a0)' from being solved.
Probable fix: use a type annotation to specify what `a0' should be.
These potential instances exist:
instance FromLangCode DE -- Defined at main.hs:32:10
instance FromLangCode EN -- Defined at main.hs:34:10
* In the second argument of `($)', namely `fromLangCode DE'
In a stmt of a 'do' block: print $ fromLangCode DE
In the expression:
do print $ de
print $ en
print $ toLangCode de
print $ toLangCode en
....
|
50 | print $ fromLangCode DE -- Output => Proxy
| ^^^^^^^^^^^^^^^
main.hs:51:11: error:
* Ambiguous type variable `a1' arising from a use of `fromLangCode'
prevents the constraint `(FromLangCode a1)' from being solved.
Probable fix: use a type annotation to specify what `a1' should be.
These potential instances exist:
instance FromLangCode DE -- Defined at main.hs:32:10
instance FromLangCode EN -- Defined at main.hs:34:10
* In the second argument of `($)', namely `fromLangCode EN'
In a stmt of a 'do' block: print $ fromLangCode EN
In the expression:
do print $ de
print $ en
print $ toLangCode de
print $ toLangCode en
....
|
51 | print $ fromLangCode EN -- Output => Proxy
| ^^^^^^^^^^^^^^^
exit status 1
My question is. Is it possible to implement it in a way so that the type annotations are no longer needed?
Updated version
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
module Main (main) where
data LangCode = DE | EN deriving (Eq, Show)
data SLangCode a where
SDE :: SLangCode DE
SEN :: SLangCode EN
data Lang (a :: LangCode) where
LangDE :: Lang 'DE
LangEN :: Lang 'EN
deriving instance Show (Lang 'DE)
deriving instance Show (Lang 'EN)
slcDE :: SLangCode a -> Lang 'DE -> Lang a
slcDE SDE t = t
slcDE SEN _ = LangEN
slcEN :: SLangCode a -> Lang 'EN -> Lang a
slcEN SEN t = t
slcEN SDE _ = LangDE
class ToLangCode a where
toLangCode :: Lang a -> LangCode
instance ToLangCode 'DE where
toLangCode _ = DE
instance ToLangCode 'EN where
toLangCode _ = EN
class FromLangCode a where
fromLangCode :: SLangCode a -> LangCode -> Lang a
instance FromLangCode 'DE where
fromLangCode SDE DE = LangDE
instance FromLangCode 'EN where
fromLangCode SEN EN = LangEN
main :: IO ()
main = do
print $ toLangCode LangDE -- Output => DE
print $ toLangCode LangEN -- Output => EN
print $ fromLangCode SDE DE -- Output => LangDE
print $ fromLangCode SEN EN -- Output => LangEN