2015-07-21 147 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