parsonsmatt / parsonsmatt.github.io

My Github pages website
Other
77 stars 25 forks source link

Compilation issue with "Return a Function to Avoid Effects" #27

Open debasishg opened 5 years ago

debasishg commented 5 years ago

Good stuff reading the post. I was trying to compile this ..

class LoadData i where
  load :: IO [i]

class SaveData o where
  save :: [o] -> IO ()

class ConvertData i o | i -> o where
  convert :: i -> o

migrate :: (LoadData i, SaveData o, ConvertData i o) => IO ()
migrate = do
  old <- load
  save (map convert old)

and got the following compiler error with the migrate function ..

Could not deduce (LoadData i0)  
  from the context: (LoadData i, SaveData o, ConvertData i o)  
bound by the type signature for:  
migrate :: forall i o.  
(LoadData i, SaveData o, ConvertData i o) =>  
IO ()

I have {-# LANGUAGE FunctionalDependencies #-} applied.

Thanks.

parsonsmatt commented 5 years ago

Oh! Yeah, there's no way for GHC to know that load is referring to the i type variable, and that save is for the o type variable.

To fix this, we need to enable ScopedTypeVariables and TypeApplications and do:

migrate :: forall i o. (LoadData i, SaveData o, ConvertData i o) => IO ()
migrate = do
  old <- load @i 
  save (map convert old)

That should fix the issue.

debasishg commented 5 years ago

Thanks Matt. Maybe we need some other extension as well .. Now it errors at @i of load -

Not in scope: type variable ‘i’

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE TypeApplications       #-}

module Migratedb where

class LoadData i where
  load :: IO [i]

class SaveData o where
  save :: [o] -> IO ()

class ConvertData i o | i -> o where
  convert :: i -> o

migrate :: (forall i o. (LoadData i, SaveData o, ConvertData i o)) => IO ()
migrate = do
  old <- load @i
  save (map convert old)
parsonsmatt commented 5 years ago

You have an extra set of parentheses - (forall i o. (...)) => IO () is different from forall i o. (...) => IO ().

debasishg commented 5 years ago

Even with this ..

migrate :: forall i o. (LoadData i, SaveData o, ConvertData i o) => IO ()
migrate = do
  old <- load @i
  save (map convert old)

I get the same error ..

/Users/debasishghosh/projects/hs/learn-haskell/src/LearnBasic1.hs:81:16: error:
    Not in scope: type variable ‘i’
   |
81 |   old <- load @i
   |                ^
parsonsmatt commented 5 years ago

Here's a full module that compiles:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE TypeApplications       #-}
{-# LANGUAGE AllowAmbiguousTypes    #-}

module Migratedb where

class LoadData i where
  load :: IO [i]

class SaveData o where
  save :: [o] -> IO ()

class ConvertData i o | i -> o where
  convert :: i -> o

migrate :: forall i o. (LoadData i, SaveData o, ConvertData i o) => IO ()
migrate = do
  old <- load @i
  save (map convert old)

We need AllowAmbiguousTypes because the type IO () doesn't refer to all of the type variables.

debasishg commented 5 years ago

ah .. that does it .. Thanks a lot Matt ..

debasishg commented 5 years ago

With AllowAmbiguousTypes it compiles, but when I try to run migrate, I get the following ..

Ambiguous type variable ‘i0’ arising from a use of ‘migrate’
  prevents the constraint ‘(LoadData i0)’ from being solved.
  Probable fix: use a type annotation to specify what ‘i0’ should be.
  These potential instance exist:
    instance LoadData Old
      -- Defined at /private/var/folders/th/mq68kp9533z31prp3x7fm7rw0000gn/T/ghc-mod11862/EffectsOnTheEdge11861-0.hs:44:10
• In the expression: migrate
  In an equation for ‘main’: main = migrate

Do we need to use equality constraints in typeclass instance with TypeFamilies extensions as in https://chrisdone.com/posts/haskell-constraint-trick/ ?

Thanks.