2015-07-21 97 views
4

假设我有一个元组,如('a',(1,("Hello",False))。为了好玩(阅读:学习),我想创建一个函数,将正确形式的一些函数应用于任何这样的元组并返回结果。用法示例:Haskell:将函数应用于嵌套2元组的函数

applyFnToTuple ('o',('t','w')) $ \a b c -> [a,b,c] == "otw" 
applyFnToTuple ('h','i') $ \a b -> [a,b] == "hi" 
applyFnToTuple ("hello",('y','o')) $ \a b c -> a ++ [b,c] 

我所做的大部分如下:

type family TupleFn ty out where 
    TupleFn (a,b) output = a -> (TupleFn b output) 
    TupleFn b output = b -> output 

class ApplyFnToTuple a where 
    applyFnToTuple :: a -> TupleFn a out -> out 

instance ApplyFnToTuple b => ApplyFnToTuple (a,b) where 
    applyFnToTuple (a,b) fn = applyFnToTuple b (fn a) 

instance ApplyFnToTuple a where 
    applyFnToTuple b fn = fn b 

的症结是,最后一个实例。我完全预计需要添加{-# OVERLAPPABLE #-},因为a(a,b)更普遍。我也很难确切地知道GHC如何解决a和我的TupleFn类的正确版本,并且知道正确的类型信号,但是我可以很容易地把它归结为我自己的缺乏理解。但在任何情况下,实际误差GHCI给我的是:

Couldn't match expected type ‘a -> out’ 
       with actual type ‘TupleFn a out’ 
    Relevant bindings include 
     fn :: TupleFn a out (bound at examples.hs:574:22) 
     b :: a (bound at examples.hs:574:20) 
     applyFnToTuple :: a -> TupleFn a out -> out 
     (bound at examples.hs:574:5) 
    The function ‘fn’ is applied to one argument, 
    but its type ‘TupleFn a out’ has none 
    In the expression: fn b 
    In an equation for ‘applyFnToTuple’: applyFnToTuple b fn = fn b 
Failed, modules loaded: none. 

据我所看到的,没有版本我TupleFn的返回的东西不带任何参数,所以我真的不理解的错误。不过,我觉得它可以做出改变的最后一个实例,以更具体的东西如简单地编译:

instance ApplyFnToTuple Char where 
    applyFnToTuple b fn = fn b 

但这意味着我不得不定义许多类似的情况等,这是不可取的。

我想知道的是,是否有一个相对简单的方法来使更普通的版本工作,以及为什么这个特定的错误?

三江源:)

PS:我运行GHC 7.10.1

+1

相关:[如何创建大多数泛型函数可能将函数应用于元组项目](http://stackoverflow.com/questions/31220903/haskell-how-to-create-most-generic-function-possible-这是应用功能) – Cirdec

+0

这是我的另一个问题。它涉及到一个元组,但并不真正相关☺ – jsdw

+6

建议:使用'(a,(b,(c,())))'来代替'(a,(b,c))'。然后很容易编写实例'ApplyFnToTuple()',它不接受任何参数并返回输出,并且没有重叠的危险。 –

回答

6

的问题是,内instance ApplyFnToTuple a的定义,不存在对没有信息是a不一个元组 - 我想GHC不会考虑如何选择实例来决定它是否是正确的定义。这意味着它不知道TupleFn给出了正确的结果,所以实例不会检查。

为了解决这个问题,你可以添加一个等式约束到告诉TupleFn是正确的。不幸的是,由于约束必须提及out类型,因此需要将它作为类的额外类型参数。至少,下面似乎工作(与GHC 7.8只测试):

{-# LANGUAGE TypeFamilies, FlexibleInstances, 
      MultiParamTypeClasses, 
      OverlappingInstances #-} 

type family TupleFn ty out where 
    TupleFn (a,b) output = a -> (TupleFn b output) 
    TupleFn b output = b -> output 

class ApplyFnToTuple a out where 
    applyFnToTuple :: a -> TupleFn a out -> out 

instance ApplyFnToTuple b out => ApplyFnToTuple (a,b) out where 
    applyFnToTuple (a,b) fn = applyFnToTuple b (fn a) 

instance TupleFn a out ~ (a -> out) => ApplyFnToTuple a out where 
    applyFnToTuple b fn = fn b 
3

像往常一样,你可以单身做到这一点,键入家庭:

{-# LANGUAGE GADTs, DataKinds, TypeFamilies, TypeOperators #-} 

type family Tuple b as where 
    Tuple b '[]  = b 
    Tuple b (a ': as) = (b, Tuple a as) 

type family Function as b where 
    Function '[]  b = b 
    Function (a ': as) b = a -> Function as b 

data SingList as where 
    SNil :: SingList '[] 
    SCons :: SingList as -> SingList (a ': as) 

applyToTuple :: SingList as -> Tuple a as -> Function (a ': as) b -> b 
applyToTuple SNil  x  f = f x 
applyToTuple (SCons as) (x, xs) f = applyToTuple as xs (f x) 

main = do 
    print $ applyToTuple (SCons (SCons SNil)) ('o',('t','w')) $ \a b c -> [a,b,c] == "otw" 
    print $ applyToTuple (SCons SNil)   ('h','i') $ \a b -> [a,b] == "hi" 
    print $ applyToTuple (SCons (SCons SNil)) ("hello",('y','o')) $ \a b c -> a ++ [b,c] 

Tuple a [b, c, d]减少了(a, (b, (c, d)))

Function [a, b, c, d] r减小到a -> b -> c -> d -> r

因此,如果as == [b, c, d],然后

Tuple a as -> Function (a ': as) r -> r 

降低到

(a, (b, (c, d))) -> (a -> b -> c -> d -> r) -> r 
2

我的最终解决方案,任何人发现这个问题:

由于DanielWagner建议,最后我更喜欢稍微调整了格式化(在元组链的末尾使用()来表示完成)。这使得它非常简单,像这样:

type family TupleFn ty out where 
    TupleFn() output = output 
    TupleFn (a,b) output = a -> (TupleFn b output) 

class ApplyFnToTuple a where 
    applyFnToTuple :: a -> TupleFn a out -> out 

instance ApplyFnToTuple b => ApplyFnToTuple (a,b) where 
    applyFnToTuple (a,b) fn = applyFnToTuple b (fn a) 

instance ApplyFnToTuple() where 
    applyFnToTuple _ fn = fn 

,这可以用于像:

applyFnToTuple ('a',('b',())) $ \a b -> [a,b] == "ab" 
applyFnToTuple ("hello",(12,('r',()))) $ \h n r -> h ++ show n ++ [r] == "hello12r" 

我希望情况可以调整;这些只是我尝试GHC喜欢的第一个:)

ØrjanJohansen的方法(请参阅他的回答)稍微复杂一点,但它提供了一个更加整洁的最终案例!

作为一个便笺,我已经想要将某些结构转换为相应的函数,但实际上我只是使用我自己的数据类型来获得额外的功能。我可以想出(不使用DataKinds现在)作为一个例子使用的,最简单的形式是:

--using DataKinds these could be done slightly neater: 
data Cons a b 
data Nil 

-- the list itself, where the type 'a' is built from the above tags 
data MyList a where 
    LCons :: itemty -> MyList a -> MyList (Cons itemty a) 
    LNil :: MyList Nil 

-- this type family converts that type 'a' to a function signature. 
type family MyListFn a output where 
    MyListFn (Cons a b) output = a -> (MyListFn b output) 
    MyListFn Nil output = output 

-- this function applies items in MyList a to a MyListFn a just 
-- like we did with tuples. Note no type family, because 
-- no type dependant differences in behaviour needed: 
applyFnToMyList :: MyList a -> MyListFn a out -> out 
applyFnToMyList (LCons a b) fn = applyFnToMyList b (fn a) 
applyFnToMyList LNil fn = fn 

具有非常类似的使用作为所述元组的情况下:

applyFnToMyList (LCons 'a' (LCons 'b' LNil)) $ \a b -> [a,b] == "ab" 
applyFnToMyList (LCons "hello" (LCons 12 (LCons 'r' LNil))) $ \h n r -> h ++ show n ++ [r] == "hello12r" 

TL; DR您可以创建函数,以完全类型安全的方式应用多态数据结构的某些元素所需的任何奇偶校验函数。真棒的东西,哈斯克尔!

+1

我想你可以通过使用普通的'()'来代替'(a,())'来简化实例。 –

+0

@ØrjanJohansen谢谢;我做了相关的更新。我以为我早些时候尝试过,但无济于事,但这一次似乎都很好:) – jsdw