不应该像Haskell这样的惰性语言允许这个定义,其中函数是curry?
apply f [] = f
apply f (x:xs) = apply (f x) xs
它基本上是一个将给定函数应用于给定参数列表的函数,并且很容易在Lisp中完成。有没有解决方法?
很难给apply
函数赋一个静态类型,因为它的类型取决于(可能是异构的)list参数的类型。至少有
两种方式
在Haskell中编写这个函数的一种方法我可以想到:
用反射
我们可以推迟应用程序的类型检查直到运行时:
import Data.Dynamic
import Data.Typeable
apply :: Dynamic -> [Dynamic] -> Dynamic
apply f [] = f
apply f (x:xs) = apply (f `dynApp` x) xs
请注意,现在Haskell程序可能会在运行时因类型错误而失败。
通过类型类递归
使用半标准的Text.Printf
技巧(由augustss,IIRC发明),解决方案可以编码in this style(运动)。虽然它可能不是很有用,但仍然需要一些技巧来隐藏列表中的类型。
编辑:我无法想出一种方法来编写它,而不使用动态类型或hlists /存在。很想看到一个例子
我喜欢Sjoerd Visscher的回复,但扩展 - 特别是IncoherentInstances
,在这种情况下用于部分应用可能 - 可能有点令人生畏。这是一个不需要任何扩展的解决方案。
首先,我们定义一个函数的数据类型,知道如何处理任意数量的参数。您应该在这里读取a
作为“参数类型”,并将b
视为“返回类型”。
data ListF a b = Cons b (ListF a (a -> b))
然后我们可以编写一些(Haskell)函数来实现这些(可变参数)函数。我将F
后缀用于恰好在Prelude中的任何函数。
headF :: ListF a b -> b
headF (Cons b _) = b
mapF :: (b -> c) -> ListF a b -> ListF a c
mapF f (Cons v fs) = Cons (f v) (mapF (f.) fs)
partialApply :: ListF a b -> [a] -> ListF a b
partialApply fs [] = fs
partialApply (Cons f fs) (x:xs) = partialApply (mapF ($x) fs) xs
apply :: ListF a b -> [a] -> b
apply f xs = headF (partialApply f xs)
例如,sum
函数可以被认为是一个可变函数:
sumF :: Num a => ListF a a
sumF = Cons 0 (mapF (+) sumF)
sumExample = apply sumF [3, 4, 5]
但是,我们还希望能够处理正常的函数,这些函数不一定知道如何处理任何数量的参数。那么该怎么办?好吧,就像Lisp一样,我们可以在运行时抛出异常。下面,我们将使用f
作为非可变函数的简单示例。
f True True True = 32
f True True False = 67
f _ _ _ = 9
tooMany = error "too many arguments"
tooFew = error "too few arguments"
lift0 v = Cons v tooMany
lift1 f = Cons tooFew (lift0 f)
lift2 f = Cons tooFew (lift1 f)
lift3 f = Cons tooFew (lift2 f)
fF1 = lift3 f
fExample1 = apply fF1 [True, True, True]
fExample2 = apply fF1 [True, False]
fExample3 = apply (partialApply fF1 [True, False]) [False]
当然,如果你不喜欢单独定义lift0
,lift1
,lift2
,lift3
等的样板,那么你需要启用一些扩展。但是如果没有他们,你可以走得很远!
以下是如何推广单个lift
函数的方法。首先,我们定义一些标准的类型级数:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeFamilies, UndecidableInstances #-}
data Z = Z
newtype S n = S n
然后介绍用于提升的类型类。您应该将类型I n a b
读作“n
a
副本作为参数,然后返回类型b
”。
class Lift n a b where
type I n a b :: *
lift :: n -> I n a b -> ListF a b
instance Lift Z a b where
type I Z a b = b
lift _ b = Cons b tooMany
instance (Lift n a (a -> b), I n a (a -> b) ~ (a -> I n a b)) => Lift (S n) a b where
type I (S n) a b = a -> I n a b
lift (S n) f = Cons tooFew (lift n f)
以下是使用f
的示例,使用广义升力重写:
fF2 = lift (S (S (S Z))) f
fExample4 = apply fF2 [True, True, True]
fExample5 = apply fF2 [True, False]
fExample6 = apply (partialApply fF2 [True, False]) [False]
不,它不能。 f
和f x
是不同的类型。由于haskell的静态类型性质,它不能承担任何功能。它必须采用特定类型的功能。
假设f
与a -> b -> c
类型一起传入。然后f x
有类型b -> c
。但a -> b -> c
必须与a -> b
具有相同的类型。因此,a -> (b -> c)
类型的函数必须是a -> b
类型的函数。所以b
必须与b -> c
相同,b -> b -> b -> ... -> c
是一个无限类型的b -> c
。它不可能存在。 (继续用b
代替{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE IncoherentInstances #-}
class Apply f a r | f -> a r where
apply :: f -> [a] -> r
instance Apply f a r => Apply (a -> f) a r where
apply f (a:as) = apply (f a) as
instance Apply r a r where
apply r _ = r
test = apply ((+) :: Int -> Int -> Int) [1::Int,2]
apply' :: (a -> a -> a) -> [a] -> a
apply' = apply
test' = apply' (+) [1,2]
)
这是在GHC中实现这一目标的一种方法。你需要在这里和那里使用一些类型的注释来说服GHC这一切都会成功。
apply f
此代码很好地说明了静态和动态类型检查之间的差异。使用静态类型检查,编译器无法确定f
是否正在传递type 'a RecFunction = RecFunction of ('a -> 'a RecFunction)
let rec apply (f: 'a RecFunction) (lst: 'a list) =
match (lst,f) with
| ([],_) -> f
| ((x::xs), RecFunction z) -> apply (z x) xs
期望的参数,因此它拒绝该程序。在lisp中,检查在运行时完成,然后程序可能会失败。
我不确定这会有多大帮助,因为我在F#中写这个,但我认为这也可以在Haskell中轻松完成:
others
在这种情况下,使用区分联合来定义所讨论的“f”,该联合允许递归数据类型定义。这可以用来解决我猜的上述问题。
在一些{-# LANGUAGE GADTs #-}
-- n represents function type, o represents output type
data LApp n o where
-- no arguments applied (function and output type are the same)
End :: LApp o o
-- intentional similarity to ($)
(:$) :: a -> LApp m o -> LApp (a -> m) o
infixr 5 :$ -- same as :
的帮助和输入下,我定义了一种方法来实现这一点(好吧,有点自定义列表类型),这与以前的答案有点不同。这是一个老问题,但它似乎仍然被访问,所以我将添加完整性的方法。
我们使用一个扩展名(GADT),列表类型与Daniel Wagner的类似,但使用标记函数类型而不是Peano数。让我们分一章来看看代码。首先,我们设置扩展并定义列表类型。数据类型是多态的,因此在此公式中,参数不必具有相同的类型。
n
让我们定义一个函数,它可以采用这样的列表并将其应用于函数。这里有一些类型的技巧:函数有类型listApply
,只有当这个类型与我们的列表类型上的n
标记匹配时,才会编译对o
的调用。通过保留我们的输出类型-- the apply function
listApply :: n -> LApp n o -> o
listApply fun End = fun
listApply fun (p :$ l) = listApply (fun p) l
未指定,我们在此留下一些自由(当创建列表时,我们不必立即完全修复它可以应用的函数类型)。
-- showing off the power of AppL
main = do print . listApply reverse $ "yrruC .B lleksaH" :$ End
print . listApply (*) $ 1/2 :$ pi :$ End
print . listApply ($) $ head :$ [1..] :$ End
print $ listApply True End
而已!我们现在可以将函数应用于存储在列表类型中的参数。预计更多? :)
listApply
不幸的是,我们有点被锁定到我们的列表类型,我们不能只转换普通列表与-- Can't do this :(
-- listApply (**) $ foldr (:$) End [2, 32]
一起使用它们。我怀疑这是类型检查器的一个基本问题(类型最终取决于列表的值)但说实话我不完全确定。
LApp
如果您对使用异构列表感到不舒服,那么您只需要在-- Alternative definition
-- data FList n o a where
-- Nil :: FList o o a
-- Cons :: a -> FList f o a -> FList (a -> f) o a
类型中添加一个额外的参数,例如:
a
这里pure f <*> [arg] <*> [arg2] ...
-- example
λ>pure (\a b c -> (a*b)+c) <*> [2,4] <*> [3] <*> [1]
[7,13]
λ>pure (+) <*> [1] <*> [2]
[3]
表示参数类型,其中应用的函数也必须接受所有相同类型的参数。
这不是您原始问题的答案,但我认为这可能是您的用例的答案。
λ>pure (+1) <*> [1..10]
[2,3,4,5,6,7,8,9,10,11]
-- Or, apply (+1) to items 1 through 10 and collect the results in a list
λ>pure (+) <*> [1..5] <*> [1..5]
[2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,5,6,7,8,9,6,7,8,9,10]
{- The applicative instance of list gives you every possible combination of
elements from the lists provided, so that is every possible sum of pairs
between one and five -}
λ>pure (\a b c -> (a*b)+c) <*> [2,4] <*> [4,3] <*> [1]
[9,7,17,13]
{- that's - 2*4+1, 2*3+1, 4*4+1, 4*3+1
Or, I am repeating argC when I call this function twice, but a and b are
different -}
λ>pure (\a b c -> show (a*b) ++ c) <*> [1,2] <*> [3,4] <*> [" look mah, other types"]
["3 look mah, other types","4 look mah, other types","6 look mah, other types","8 look mah, other types"]
列表的应用实例比这个超级狭窄的用例要广泛得多......
qazxswpoi
因此,它不是同一个概念,而是很多那些组合用例,并增加了一些。