我很好奇是否可以编写一个函数apply_nth
,它接受一个函数,一个参数的数量和该参数的值,然后返回一个新的,部分应用的函数。 / p>
我得到的感觉是,由于类型系统,这是不可能的,但我无法得出令人满意的答案。我也无法提出工作型签名。
如果语言更松散,我想代码可能看起来像这样。
apply_nth f 0 x = f x
apply_nth f n x = \a -> apply_nth (f a) (n-1) x
有什么想法吗?
答案 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),那么程序可能会出现段错误。