是否可以在Haskell中部分应用第n个参数?

时间:2015-10-22 21:37:14

标签: haskell functional-programming generic-programming dependent-type

我很好奇是否可以编写一个函数apply_nth,它接受​​一个函数,一个参数的数量和该参数的值,然后返回一个新的,部分应用的函数。 / p>

我得到的感觉是,由于类型系统,这是不可能的,但我无法得出令人满意的答案。我也无法提出工作型签名。

如果语言更松散,我想代码可能看起来像这样。

apply_nth f 0 x = f x
apply_nth f n x = \a -> apply_nth (f a) (n-1) x

有什么想法吗?

4 个答案:

答案 0 :(得分:8)

不是奇怪的类型家庭,但也不是超级好的:

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

import Data.Proxy

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

data SL as where
    Sn :: SL '[]
    Sc :: SL as -> SL (a ': as)

applyN :: Proxy c -> SL as -> Fun as (b -> c) -> b -> Fun as c
applyN p  Sn    f y = f y
applyN p (Sc s) f y = \x -> applyN p s (f x) y

main = print $ applyN Proxy (Sc (Sc Sn)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1]

我们还可以将Proxy c打包到SL

data SL as c where
    Sn :: SL '[] c
    Sc :: SL as c -> SL (a ': as) c

applyN :: SL as c -> Fun as (b -> c) -> b -> Fun as c
applyN  Sn    f y = f y
applyN (Sc s) f y = \x -> applyN s (f x) y

main = print $ applyN (Sc (Sc Sn)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1]

或者你可以简单地定义几个组合器:

z = id
s r f y x = r (f x) y
applyN = id

main = print $ applyN (s (s z)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1]

答案 1 :(得分:7)

你的感觉是正确的,这是不可能的。部分应用程序更改函数的类型,以何种方式取决于您应用的参数。但是,如果该参数仅在运行时使用额外参数进行索引,则编译器不知道该类型将是什么,并且编译器必须对所有进行类型检查。实际上,您需要结果为dependent type,但Haskell不是依赖类型的语言。

现在,实际上,如果你抛出几个GHC扩展并引入几个奇怪的类型系列,那么你实际上可以实现类似于这种依赖类型的东西。但老实说,我怀疑这是一个好主意。无论如何你还需要什么?如果您使用超过8个参数来处理函数,那么您可能会做错事,并且为了更简单的函数,您可以定义8个组合器,每个组合器应用一个固定的参数位置。

或者:类似的功能可能是合理的

apply_nth :: ([a] -> b) -> Int -> a -> [a] -> b
apply_nth f i a xs = f $ before ++ [a] ++ after
 where (before, after) = splitAt i xs

与参数列表不同,值列表很容易长达数百个元素,因此在这种情况下预先应用在运行时索引的单个元素是有意义的。

这不仅仅是一个安全预防措施 - 它是必要的,因为类型甚至不存在于运行时,因此编译器需要完成准备所有可能依赖于类型的条件。这就是为什么Haskell是安全的简明快速可扩展的原因,就像其他几种语言一样。

答案 2 :(得分:4)

当然,有点类型"魔术":

{-# LANGUAGE DataKinds, KindSignatures, UndecidableInstances #-} 

data Nat = Z | S Nat 

data SNat (n :: Nat) where 
  SZ :: SNat Z 
  SS :: SNat n -> SNat (S n) 

class ApplyNth (n :: Nat) arg fn fn' | n arg fn -> fn', n fn -> arg where 
  applyNth :: SNat n -> arg -> fn -> fn' 

instance ApplyNth Z a (a -> b) b where 
  applyNth SZ a f = f a 

instance ApplyNth n arg' fn fn' => ApplyNth (S n) arg' (arg0 -> fn) (arg0 -> fn') where 
  applyNth (SS n) a f = \a0 -> applyNth n a (f a0)

applyNth的一般类型表示,它采用索引(自然数 - 在类型中编码),参数,函数,并返回一个函数。

请注意两个功能依赖项。第一个说,给定索引,参数和输入函数,输出函数的类型是已知的。这很明显。第二个说,给定索引和输入函数,ApplyNth能够查看函数内部并找出它需要的参数!

此函数与类型推断非常相似:

>:t \x -> applyNth (SS SZ) x (^)
\x -> applyNth (SS SZ) x (^)
  :: (Num fn', Integral b) => b -> fn' -> fn'
>:t applyNth (SS SZ) 0 (^)
applyNth (SS SZ) 0 (^) :: Num fn' => fn' -> fn'
>:t applyNth (SS SZ) (0 :: Integer) (^)
applyNth (SS SZ) (0 :: Integer) (^) :: Num fn' => fn' -> fn'
>:t applyNth (SS SZ) ('a' :: Char) (^)

<interactive>:1:32: Warning:
    Could not deduce (Integral Char) arising from a use of `^'
    ...
applyNth (SS SZ) ('a' :: Char) (^) :: Num fn' => fn' -> fn'
>let squared = applyNth (SS SZ) 2 (^)
>:t squared
squared :: Num fn' => fn' -> fn'
>squared 3
9
>squared 100
10000
>let f a b c d e = mapM_ putStrLn 
      [ show n ++ ": " ++ x 
      | (n,x) <- zip [0..]  
          [show a, show b, show c, show d, show e] ]
>applyNth SZ 'q' $ 
 applyNth (SS $ SZ) [1,8,42] $ 
 applyNth SZ (True, 10) $ 
 applyNth (SS $ SS $ SS SZ) "abcd" $ 
 applyNth (SS $ SS $ SS SZ) pi $
 f
0: (True,10)
1: 'q'
2: [1,8,42]
3: 3.141592653589793
4: "abcd"

您也可以以运营商形式定义它:

infixl 9 =:
(=:) :: ApplyNth n arg fn fn' => SNat n -> arg -> fn -> fn' 
(=:) = applyNth

r = 
 SZ =: 'q' $ 
 SS SZ =: [1,8,42] $ 
 SZ =: (True, 10) $ 
 (SS $ SS $ SS SZ) =: "abcd" $ 
 (SS $ SS $ SS SZ) =: pi $ 
 f

答案 3 :(得分:1)

不在任何称为“Haskell”的语言中,但是如果你看看Glasgow Haskell,包括不安全的函数,那么你可以按照你想要的方式部分应用......那么你必须正确地指定参数位置。这是一个可怕的黑客。不要这样做,除非你非常舒服......好吧..不要这样做。

当我问一个类似的问题(Using Typeable to partially apply function at run-time (any time types match))时,这段代码就从后面开始。

import Unsafe.Coerce

testBuild :: String
testBuild = let f = buildFunc typedFunction ("argument 'b'", 42::Int) 1
            in f "Look I have applied " " and it seems to work."

typedFunction :: String -> (String,Int) -> String -> String
typedFunction = (\a b c -> a ++ show b ++ c)

buildFunc :: f -> x -> Int -> g
buildFunc f x 0 = unsafeCoerce f x
buildFunc f x i =
      let res = \y -> (buildFunc (unsafeCoerce f y) x (i-1))
      in unsafeCoerce res

输出:

*Main> testBuild
"Look I have applied (\"argument 'b'\",42) and it seems to work."

请注意,如果我们错误地指定了参数index(1),那么程序可能会出现段错误。