Haskell:将一些函数应用于嵌套的2元组的函数

时间:2015-07-21 19:54:36

标签: haskell type-families

假设我有一个像.item { position: relative; display: inline-block; width: 20%; height: 100px; background-color: green; margin-right: 10px; transition: all 0.4s ease; } .item:hover { transform: scale(1.5); margin-right: 6%; /* fallback for ancient browsers that don't support calc() */ margin-right: calc(5% + 10px); margin-left: 5%; } 这样的元组。只是为了好玩(阅读:学习),我想创建一个函数,将一些正确形式的函数应用于任何这样的元组并返回结果。用法示例:

('a',(1,("Hello",False))

我完成了大部分内容如下:

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更通用。我也很难确切地看到GHC如何解决(a,b)以及a类的正确版本并知道正确的类型sig,但我可以很容易地将其归结为我自己缺乏理解。但无论如何,GHCI给我的实际错误是:

TupleFn

据我所知,我的TupleFn没有版本返回没有参数的东西,所以我真的不明白错误。但是,我发现只需将最后一个实例更改为更具体的例子即可编译:

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.

但这意味着我必须定义许多类似的实例,这是不可取的。

我想知道的是,是否有一种相对简单的方法可以使更通用的版本工作,以及为什么会出现这种特殊错误?

谢谢你:)

PS:我正在运行GHC 7.10.1

3 个答案:

答案 0 :(得分: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 

答案 1 :(得分: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 :(得分: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 您可以创建函数,以完全类型安全的方式应用多态数据结构的某些元素所需的奇偶校验函数。很棒的东西,Haskell!