Haskell中的模糊实例解析

时间:2014-09-15 18:02:29

标签: haskell types type-inference typeclass type-families

简介和示例用例

您好!我在Haskell遇到了问题。我们考虑以下代码

class PolyMonad m1 m2 m3 | m1 m2 -> m3 where
    polyBind :: m1 a -> (a -> m2 b) -> m3 b

只声明了poly monad绑定。一个很好的示例用例场景是:

newtype Pure a = Pure { fromPure :: a } deriving (Show)

instance PolyMonad Pure Pure Pure where
    polyBind a f = f (fromPure a)

instance PolyMonad Pure IO IO where
    polyBind a f = f (fromPure a)

instance PolyMonad IO Pure IO where
    polyBind a f = (fromPure . f) <$> a

instance PolyMonad IO IO IO where
    polyBind a f = a >>= f

并将其与-XRebindableSyntax一起使用,如下所示:

test = do
    Pure 5
    print "hello"
    Pure ()

但我们可以用它做更多的事情 - 这只是一个测试,向您展示一个示例案例。

问题

让我们考虑一些更复杂的用法。我想写一个类似polymonad的类,它不会总是输出m3 b,但在某些特定情况下,它会为特定的m3 (X b)输出X。为简单起见,我们假设只有在m3 (X b)m1m2时才输出IO

似乎我们现在无法在Haskell中做到这一点而不会失去灵活性。 我需要在不添加任何类型信息的情况下编译以下函数(Haskell代码是生成的代码):

tst1 x = x `polyBind` (\_ -> Pure 0)
tst2 = (Pure 1) `polyBind` (\_ -> Pure 0)
tst3 x y = x `polyBind` (\_ -> y `polyBind` (\_ -> Pure 0))

无论如何,这些函数使用PolyMonad类编译得很好。

Fundep尝试解决问题

其中一次尝试可能是:

class PolyMonad2 m1 m2 m3 b | m1 m2 b -> out where
    polyBind2 :: m1 a -> (a -> m2 b) -> out

当然我们可以轻松编写所有必要的实例,例如:

instance PolyMonad2 Pure Pure b (Pure b) where
    polyBind2 a f = f (fromPure a)

instance PolyMonad2 Pure IO b (IO (X b)) where
    polyBind2 a f = fmap X $ f (fromPure a)

-- ...

但是在使用polyBind2而不是polyBind时,我们的测试函数无法编译。第一个函数(tst1 x = x polyBind2 (\_ -> Pure 0))输出编译错误:

Could not deduce (PolyMonad2 m1 Pure b0 out)
  arising from the ambiguity check for ‘tst1’
from the context (PolyMonad2 m1 Pure b out, Num b)
  bound by the inferred type for ‘tst1’:
             (PolyMonad2 m1 Pure b out, Num b) => m1 a -> out
  at /tmp/Problem.hs:51:1-37
The type variable ‘b0’ is ambiguous
When checking that ‘tst1’
  has the inferred type ‘forall (m1 :: * -> *) b out a.
                         (PolyMonad2 m1 Pure b out, Num b) =>
                         m1 a -> out’
Probable cause: the inferred type is ambiguous

封闭式家庭尝试解决问题

稍微好一点的方法是在这里使用closed type families,例如:

class PolyMonad3 m1 m2 where
    polyBind3 :: m1 a -> (a -> m2 b) -> OutputOf m1 m2 b

type family OutputOf m1 m2 a where
    OutputOf Pure Pure a = Pure a
    OutputOf x    y    a = Pure (X a)

但是在尝试编译tst1函数(tst1 x = x polyBind3 (\_ -> Pure 0))时,我们得到另一个编译时错误:

Could not deduce (OutputOf m1 Pure b0 ~ OutputOf m1 Pure b)
from the context (PolyMonad3 m1 Pure, Num b)
  bound by the inferred type for ‘tst1’:
             (PolyMonad3 m1 Pure, Num b) => m1 a -> OutputOf m1 Pure b
  at /tmp/Problem.hs:59:1-37
NB: ‘OutputOf’ is a type function, and may not be injective
The type variable ‘b0’ is ambiguous
Expected type: m1 a -> OutputOf m1 Pure b
  Actual type: m1 a -> OutputOf m1 Pure b0
When checking that ‘tst1’
  has the inferred type ‘forall (m1 :: * -> *) a b.
                         (PolyMonad3 m1 Pure, Num b) =>
                         m1 a -> OutputOf m1 Pure b’
Probable cause: the inferred type is ambiguous

围绕

进行的hacky尝试

我找到了另一个解决方案,但是hacky并且最终无法正常工作。但它非常有趣。让我们考虑以下类型类:

class PolyMonad4 m1 m2 b out | m1 m2 b -> out, out -> b where
    polyBind4 :: m1 a -> (a -> m2 b) -> out

当然功能依赖out -> b是错误的,因为我们无法定义像这样的实例:

instance PolyMonad4 Pure IO b (IO (X b)) where
    polyBind4 a f = fmap X $ f (fromPure a)

instance PolyMonad4 IO IO b (IO b) where
    polyBind4 = undefined

但是让我们玩它并声明它们(使用-XUndecidableInstances):

instance out~(Pure b) => PolyMonad4 Pure Pure b out where
    polyBind4 a f = f (fromPure a)

instance out~(IO(X b)) => PolyMonad4 Pure IO b out where
    polyBind4 a f = fmap X $ f (fromPure a)

instance out~(IO b) => PolyMonad4 IO IO b out where
    polyBind4 = undefined

instance out~(IO(X b)) => PolyMonad4 IO Pure b out where
    polyBind4 = undefined

有趣的是,我们的一些测试功能可以编译和工作,即:

tst1' x = x `polyBind4` (\_ -> Pure 0)
tst2' = (Pure 1) `polyBind4` (\_ -> Pure 0)

但这不是:

tst3' x y = x `polyBind4` (\_ -> y `polyBind4` (\_ -> Pure 0))

导致编译时错误:

Could not deduce (PolyMonad4 m3 Pure b0 (m20 b))
  arising from the ambiguity check for ‘tst3'’
from the context (PolyMonad4 m3 Pure b1 (m2 b),
                  PolyMonad4 m1 m2 b out,
                  Num b1)
  bound by the inferred type for ‘tst3'’:
             (PolyMonad4 m3 Pure b1 (m2 b), PolyMonad4 m1 m2 b out, Num b1) =>
             m1 a -> m3 a1 -> out
  at /tmp/Problem.hs:104:1-62
The type variables ‘m20’, ‘b0’ are ambiguous
When checking that ‘tst3'’
  has the inferred type ‘forall (m1 :: * -> *)
                                (m2 :: * -> *)
                                b
                                out
                                a
                                (m3 :: * -> *)
                                b1
                                a1.
                         (PolyMonad4 m3 Pure b1 (m2 b), PolyMonad4 m1 m2 b out, Num b1) =>
                         m1 a -> m3 a1 -> out’
Probable cause: the inferred type is ambiguous

使用newtype wrap更加hacky尝试

我告诉它更加hacky,因为它导致我们使用-XIncoherentInstancesJust (Pure evil)。其中一个想法当然是编写newtype包装器:

newtype XWrapper m a = XWrapper (m (X (a)))

和一些用于解压缩的工具:

class UnpackWrapper a b | a -> b where
    unpackWrapper :: a -> b

instance UnpackWrapper (XWrapper m a) (m (X a)) where
    unpackWrapper (XWrapper a) = a

instance UnpackWrapper (Pure a) (Pure a) where
    unpackWrapper = id

instance UnpackWrapper (IO a) (IO a) where
    unpackWrapper = id

现在我们可以轻松地声明以下实例:

instance PolyMonad Pure Pure Pure 
instance PolyMonad Pure IO (XWrapper IO) 
instance PolyMonad IO Pure (XWrapper IO) 
instance PolyMonad IO IO IO 

但同样,在组合bind和unwrap函数时我们无法运行测试:

polyBindUnwrap a f = unpackWrapper $ polyBind a f

测试函数无法再次编译。我们可以在这里弄乱一些-XIncoherentInstances(参见最后的代码清单),但到目前为止我没有得到任何好的结果。

最后一个问题

这是使用当前GHC Haskell实现无法完成的问题吗?

完整代码列表

这是一个完整的代码清单,可以在GHC&gt; = 7.8:

中运行
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Applicative

class PolyMonad m1 m2 m3 | m1 m2 -> m3 where
    polyBind :: m1 a -> (a -> m2 b) -> m3 b


----------------------------------------------------------------------
-- Some utils
----------------------------------------------------------------------

newtype Pure a = Pure { fromPure :: a } deriving (Show)
newtype X a = X { fromX :: a } deriving (Show)

main = return ()

----------------------------------------------------------------------
-- Example use cases
----------------------------------------------------------------------

instance PolyMonad Pure Pure Pure where
    polyBind a f = f (fromPure a)

instance PolyMonad Pure IO IO where
    polyBind a f = f (fromPure a)

instance PolyMonad IO Pure IO where
    polyBind a f = (fromPure . f) <$> a

instance PolyMonad IO IO IO where
    polyBind a f = a >>= f

-- works when using rebindable syntax
--test = do
--    Pure 5
--    print "hello"
--    Pure ()

tst1 x = x `polyBind` (\_ -> Pure 0)
tst2 = (Pure 1) `polyBind` (\_ -> Pure 0)
tst3 x y = x `polyBind` (\_ -> y `polyBind` (\_ -> Pure 0))

----------------------------------------------------------------------
-- First attempt to solve the problem
----------------------------------------------------------------------


class PolyMonad2 m1 m2 b out | m1 m2 b -> out where
    polyBind2 :: m1 a -> (a -> m2 b) -> out


instance PolyMonad2 Pure Pure b (Pure b) where
    polyBind2 a f = f (fromPure a)

instance PolyMonad2 Pure IO b (IO (X b)) where
    polyBind2 a f = fmap X $ f (fromPure a)

-- ...

-- tst1 x = x `polyBind2` (\_ -> Pure 0) -- does NOT compile


----------------------------------------------------------------------
-- Second attempt to solve the problem
----------------------------------------------------------------------

class PolyMonad3 m1 m2 where
    polyBind3 :: m1 a -> (a -> m2 b) -> OutputOf m1 m2 b

type family OutputOf m1 m2 a where
    OutputOf Pure Pure a = Pure a
    OutputOf x    y    a = Pure (X a)

-- tst1 x = x `polyBind3` (\_ -> Pure 0) -- does NOT compile


----------------------------------------------------------------------
-- Third attempt to solve the problem
----------------------------------------------------------------------

class PolyMonad4 m1 m2 b out | m1 m2 b -> out, out -> b where
    polyBind4 :: m1 a -> (a -> m2 b) -> out


instance out~(Pure b) => PolyMonad4 Pure Pure b out where
    polyBind4 a f = f (fromPure a)

instance out~(IO(X b)) => PolyMonad4 Pure IO b out where
    polyBind4 a f = fmap X $ f (fromPure a)

instance out~(IO b) => PolyMonad4 IO IO b out where
    polyBind4 = undefined

instance out~(IO(X b)) => PolyMonad4 IO Pure b out where
    polyBind4 = undefined


tst1' x = x `polyBind4` (\_ -> Pure 0)
tst2' = (Pure 1) `polyBind4` (\_ -> Pure 0)
--tst3' x y = x `polyBind4` (\_ -> y `polyBind4` (\_ -> Pure 0)) -- does NOT compile


----------------------------------------------------------------------
-- Fourth attempt to solve the problem
----------------------------------------------------------------------

class PolyMonad6 m1 m2 m3 | m1 m2 -> m3 where
    polyBind6 :: m1 a -> (a -> m2 b) -> m3 b

newtype XWrapper m a = XWrapper (m (X (a)))


class UnpackWrapper a b | a -> b where
    unpackWrapper :: a -> b

instance UnpackWrapper (XWrapper m a) (m (X a)) where
    unpackWrapper (XWrapper a) = a

instance UnpackWrapper (Pure a) (Pure a) where
    unpackWrapper = id

instance UnpackWrapper (IO a) (IO a) where
    unpackWrapper = id

--instance (a1~a2, out~(m a2)) => UnpackWrapper (m a1) out where
--    unpackWrapper = id


--{-# LANGUAGE OverlappingInstances #-}
--{-# LANGUAGE IncoherentInstances #-}

instance PolyMonad6 Pure Pure Pure where
    polyBind6 = undefined

instance PolyMonad6 Pure IO (XWrapper IO) where
    polyBind6 = undefined

instance PolyMonad6 IO Pure (XWrapper IO) where
    polyBind6 = undefined

instance PolyMonad6 IO IO IO where
    polyBind6 = undefined

--polyBind6' a f = unpackWrapper $ polyBind6 a f

--tst1'' x = x `polyBind6'` (\_ -> Pure 0)
--tst2'' = (Pure 1) `polyBind4` (\_ -> Pure 0)
--tst3'' x y = x `polyBind4` (\_ -> y `polyBind4` (\_ -> Pure 0)) -- does NOT compile

2 个答案:

答案 0 :(得分:3)

我不认为这个问题取决于内射型家庭。

在封闭类型族OutputOf周围的错误消息中提到了内射类型族位。但是,这个函数确实不是单射的 - 它的第二个等式允许任何xy。 GHC喜欢提醒用户,它不会对类型系列进行注入性分析,但有时候(就像这里一样),这个警告没有帮助。

相反,您遇到的问题似乎都源于数据过载。当您说Pure 0时,GHC会正确推断类型Num a => Pure a。问题是你正在访问的类型级功能(类型类解析,功能依赖,类型系列)非常关心这里为a做出的具体选择。例如,您的任何一种方法很可能对Int的行为与对Integer的行为不同。 (例如,PolyMonad2中可能有OutputOf或额外方程的不同实例。)

所有这一切的解决方案可能是使用RebindableSyntax并将fromInteger定义为单态,从而修复数字类型并避免麻烦。

答案 1 :(得分:2)

我认为根本区别在于:

class PolyMonad m1 m2 m3 | m1 m2 -> m3 where
    polyBind :: m1 a -> (a -> m2 b) -> m3 b

b是完全多态的;它不是类型类的参数,因此可以选择实例并应用函数依赖关系来m3m1确定m2,而不依赖于b 。它也出现在两个地方;如果类型推断器知道结果类型传递给polyBind的函数类型,那么它可以充分确定b。像Num b => b这样的类型会愉快地流过&#34; polyBind的许多应用程序,直到它用于修复具体类型的地方。虽然我认为这可能只是单态限制默认类型,在这种情况下可以避免模糊类型变量错误(正是它的设计目的)。

而在这里:

class PolyMonad2 m1 m2 m3 b | m1 m2 b -> out where
    polyBind2 :: m1 a -> (a -> m2 b) -> out

b显示为类型类参数。如果我们要尝试推断out是什么,我们需要完全确定b才能选择实例。并且b没有理由对out类型的结构承担任何特定的关系(或者更确切地说,对于每个单独的实例,这种关系可能是不同的,这毕竟是你和#39;重新尝试实现),因此无法按照b通过&#34;一系列polyBind2来电,除非您已完全解决所有个实例。

如果b是多态数Num b => bout受其使用限制为Num c => m c形式(对于某些类型构造函数m ),没有理由cb必须是相同的 Num实例。因此,在对数字进行操作的一系列polyBind2调用链接中,每个中间结果都可以使用不同的Num实例,并且不知道它们中的任何一个都没有选择正确PolyMonad2个实例的方法,这些实例会将bout中的内容统一起来。类型默认仅适用于变量的所有约束都是数字前置类,但此处b涉及约束PolyMonad2 m1 m2 m3 b,因此它不能默认(这可能是一个好的事实上,因为您选择的确切类型可能会影响使用哪个实例并显着改变程序行为;它只是已知为彼此的近似值的数字类,因此,如果程序对于使用哪个实例是模棱两可的,然后只是随意选择一个而不是抱怨模糊性,这是不合理的。)

据我所知,从outm1m2确定b的任何方法都是如此,它是否具有功能依赖性,键入家庭或其他东西。我不知道如何在这里实际解决这个问题,而不提供更多的类型注释。