2013-05-16 2 views
6

수가 다른 기능을 위해, 나는 다른 함수를 호출하는 기능을 다음 있습니다 :Typeclass 내 간단한 하스켈 DSL에서 인수

callF :: forall a. (Typeable a) 
    => (V a) -> (V a) 
callF [email protected](V (FunP name)) = 
    pack $ FunAppl (prettyV fp) [] 

callF1 :: forall a b. (Typeable a, Typeable b) 
    => (V (V a -> V b)) -> V a -> (V b) 
callF1 [email protected](V (FunP name)) arg = 
    pack $ FunAppl (prettyV fp) [prettyV arg] 

callF2 :: forall a b c. (Typeable a, Typeable b, Typeable c) 
    => (V (V a -> V b -> V c)) -> V a -> V b -> (V c) 
callF2 [email protected](V (FunP name)) arg1 arg2 = 
    pack $ FunAppl (prettyV fp) [prettyV arg1, prettyV arg2] 

내가 typeclasses를 사용하여 인수의 수에 대해이 작업을 일반화하고 싶습니다.

이것은 내가 시도한 기능이지만 0 또는 1 인수에서만 작동하며 올바른 인수 개수로 함수를 호출하지 않습니다.

class Callable a b | a -> b where 
    call :: V a -> [String] -> b 

instance Callable a (V b) where 
    call [email protected](V (FunP name)) x = pack $ FunAppl (prettyV fp) x 

instance (Typeable c, Typeable d) => Callable (V a -> V b) (V c -> V d) where 
    call [email protected](V (FunP name)) list arg = call fun (list ++ [prettyV arg]) 
+2

'V a -> V b -> V c'는 실제로 'V a -> (V b -> V c)'입니다. 유도를 통해이를 수행 할 수 있습니다. 기본 경우 :'V a'는 호출 가능합니다. 귀납적 인 경우 :'V a'가 호출 가능하다면,'V b -> V a'가 호출 가능합니다. 귀납적 인 경우의 인스턴스 헤드는'instance Callable k => Callable (m -> k) where'의 일반적인 형식을가집니다. –

답변

9

여러 인수와 기능에 대한 일반적인 기술 - printf 같은 재귀 typeclass를 사용하는 --is. printf의 경우 PrintfType 클래스로이 작업을 수행합니다.

(PrintfArg a, PrintfType r) => PrintfType (a -> r) 

이 기본적으로 당신이 PrintfType을 반환 할 경우, 함수는 또한 인스턴스 말한다 : 중요한 통찰력은 재귀 인스턴스입니다. "기본 케이스"는 String과 같은 유형입니다. 따라서 printf과 하나의 인수로 호출하려는 경우 PrintfType StringPrintfType (a -> r) 인 경우 이 String 인 두 개의 인스턴스가 발생합니다. 당신이 인수를 원하는 경우, 이동 : String, (a -> r)rStringr 이전 (a -> r)입니다 (a -> r)입니다.

그러나 실제로 문제는 좀 더 복잡합니다. 2 개의 작업을 처리하는 인스턴스가 필요합니다. 인스턴스가 다른 유형의 함수 (예 : V (V a -> V b), V (V a -> V b -> V c) 등)에 적용되도록하고 적절한 수의 인수가 표시되도록합니다.

첫 번째 단계는 [String]을 사용하여 인수를 전달하는 것입니다. 유형은 값의 개수에 대한 정보를 잃어 버리기 때문에 적절한 수의 인수가 있는지 확인할 수 없습니다. 대신, 인수의 개수를 반영하는 인수 목록에 형식을 사용해야합니다.

이 유형은 다음과 같이 보일 수 있습니다 : 그것은 두 개의 다른과 같이 사용할 수있는 유형, 결합하는 단지 형

data a :. b = a :. b 

입니다 :

"foo" :. "bar"   :: String :. String 
"foo" :. "bar" :. "baz" :: String :. String :. String 

지금 방금 작성해야 형식 수준의 인수 목록과 함수 자체를 탐색하는 재귀 적 인스턴스가있는 typecass입니다. 내 말은 매우 거친 독립형 스케치입니다. 당신은 당신 자신의 특별한 문제에 그것을 채택해야 할 것입니다.

infixr 8 :. 
data a :. b = a :. b 

class Callable f a b | f -> a b where 
    call :: V f -> a -> b 

instance Callable rf ra (V rb) => Callable (String -> rf) (String :. ra) (V rb) where 
    call (V f) (a :. rest) = call (V (f a)) rest 

instance Callable String() (V String) where 
    call (V f)() = V f 

당신은 또한 몇 가지 확장 기능을 활성화해야합니다 : FlexibleInstances, FucntionalDepenedenciesUndecidableInstances을.

그런 다음이처럼 사용할 수 있습니다

당신이 잘못된 수의 인수에 전달하면
*Main> call (V "foo")() 
V "foo" 
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :.()) 
V "foo bar" 
*Main> call (V (\ x y -> "foo " ++ x ++ y)) ("bar" :. " baz" :.()) 
V "foo bar baz" 

, 당신은 유형의 오류가 발생합니다. 틀림없이, 그것은 세상에서 가장 예쁜 오류 메시지가 아닙니다! 즉, 오류 (Couldn't match type `()' with `[Char] :.()') 의 중요한 부분은이 핵심 문제 (일치하지 않는 인수 목록)를 지적했기 때문에 따라야합니다. 이 문제에 대한 최고의 해결책 확신하지 했는데요 -이 특정 작업에-복잡 통해 비트가 될 수도

*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 

<interactive>:101:1: 
    Couldn't match type `()' with `[Char] :.()' 
    When using functional dependencies to combine 
     Callable String() (V String), 
     arising from the dependency `f -> a b' 
     in the instance declaration at /home/tikhon/Documents/so/call.hs:16:14 
     Callable [Char] ([Char] :.()) (V [Char]), 
     arising from a use of `call' at <interactive>:101:1-4 
    In the expression: 
     call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 
    In an equation for `it': 
     it = call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :.()) 

참고. 그러나 좀 더 진보 된 typeclass 기능을 사용하여보다 복잡한 유형 수준의 불변성을 적용하는 것은 아주 좋은 연습입니다.