是否可以部分应用 Haskell 中的第 n 个参数?
Is it possible to partially apply nth parameter in Haskell?
我很好奇是否可以编写一个函数 apply_nth
接受一个函数、参数的数量和该参数的值,然后 returns 一个新的、部分应用的函数.
我的感觉是,由于类型系统的原因,这是不可能的,但我想不出一个令人满意的答案。我也想不出一个有效的类型签名。
如果语言类型更松散,我想代码可能看起来像这样。
apply_nth f 0 x = f x
apply_nth f n x = \a -> apply_nth (f a) (n-1) x
有什么想法吗?
你的感觉是对的,这不可能。部分应用程序会更改函数的类型,并且以何种方式取决于您应用的参数。但是,如果该参数仅在运行时使用额外参数进行索引,编译器将不知道类型是什么,并且编译器必须对所有内容进行类型检查†。确实,您需要结果具有 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 安全 and 简洁 and 快速 and 可扩展语言。
当然,有点类型 class "magic":
{-# 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
的一般类型表示,它需要一个索引(一个自然数 - 在类型中编码)、一个参数、一个函数和 returns 一个函数。
注意两个函数依赖关系。第一个说给定索引、参数和输入函数,输出函数的类型是已知的。这是显而易见的。第二个说,给定索引和输入函数,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
不在任何称为 "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."
注意,如果我们错误地指定了参数索引 (1),那么程序可能会出现段错误。
不是那种奇怪的家庭,但也不是特别好:
{-# 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]
我很好奇是否可以编写一个函数 apply_nth
接受一个函数、参数的数量和该参数的值,然后 returns 一个新的、部分应用的函数.
我的感觉是,由于类型系统的原因,这是不可能的,但我想不出一个令人满意的答案。我也想不出一个有效的类型签名。
如果语言类型更松散,我想代码可能看起来像这样。
apply_nth f 0 x = f x
apply_nth f n x = \a -> apply_nth (f a) (n-1) x
有什么想法吗?
你的感觉是对的,这不可能。部分应用程序会更改函数的类型,并且以何种方式取决于您应用的参数。但是,如果该参数仅在运行时使用额外参数进行索引,编译器将不知道类型是什么,并且编译器必须对所有内容进行类型检查†。确实,您需要结果具有 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 安全 and 简洁 and 快速 and 可扩展语言。
当然,有点类型 class "magic":
{-# 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
的一般类型表示,它需要一个索引(一个自然数 - 在类型中编码)、一个参数、一个函数和 returns 一个函数。
注意两个函数依赖关系。第一个说给定索引、参数和输入函数,输出函数的类型是已知的。这是显而易见的。第二个说,给定索引和输入函数,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
不在任何称为 "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."
注意,如果我们错误地指定了参数索引 (1),那么程序可能会出现段错误。
不是那种奇怪的家庭,但也不是特别好:
{-# 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]