我如何定义Lisp在Haskell中的应用?
这个定义不应该像Haskell这样一个懒惰的语言被允许,在这个语言中,函数是被curry的吗?
apply f [] = f apply f (x:xs) = apply (fx) xs
它基本上是一个函数,它将给定的函数应用于给定的参数列表,在Lisp中很容易完成。 有没有解决办法?
因为它的types取决于(可能是异构的)列表参数的types,所以很难给予apply
函数一个静态types。 在Haskell中至less有两种方法来编写这个函数,我可以这样想:
使用reflection
我们可以推迟应用程序的types检查,直到运行时:
import Data.Dynamic import Data.Typeable apply :: Dynamic -> [Dynamic] -> Dynamic apply f [] = f apply f (x:xs) = apply (f `dynApp` x) xs
请注意,现在Haskell程序可能会在运行时出现types错误。
通过类recursionrecursion
使用半标准的Text.Printf
技巧( Text.Printf
发明,IIRC),可以用这种风格 (练习)来编码解决scheme。 虽然它可能不是很有用,但仍然需要一些技巧来隐藏列表中的types。
编辑:我不能想出一个方法来写这个,没有使用dynamictypes或HL / existentials。 很想看到一个例子
我喜欢Sjoerd Visscher的回复,但是扩展 – 特别是IncoherentInstances
,在这种情况下使用部分应用程序 – 可能有点令人生畏。 这是一个不需要任何扩展的解决scheme。
首先,我们定义一个函数的数据types,知道如何处理任意数量的参数。 你应该读a
这里的“参数types”, b
是“返回types”。
data ListF ab = Cons b (ListF a (a -> b))
然后,我们可以编写一些(Haskell)函数来模拟这些(可变参数)函数。 我使用F
后缀来处理碰巧在Prelude中的任何函数。
headF :: ListF ab -> b headF (Cons b _) = b mapF :: (b -> c) -> ListF ab -> ListF ac mapF f (Cons v fs) = Cons (fv) (mapF (f.) fs) partialApply :: ListF ab -> [a] -> ListF ab partialApply fs [] = fs partialApply (Cons f fs) (x:xs) = partialApply (mapF ($x) fs) xs apply :: ListF ab -> [a] -> b apply f xs = headF (partialApply f xs)
例如, sum
函数可以被认为是一个可变函数:
sumF :: Num a => ListF aa sumF = Cons 0 (mapF (+) sumF) sumExample = apply sumF [3, 4, 5]
但是,我们也希望能够处理正常的函数,这些函数不一定知道如何处理任何数量的参数。 那么,该怎么办? 那么,就像Lisp一样,我们可以在运行时抛出exception。 下面,我们将使用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
function。 首先,我们定义一些标准的types级数字:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeFamilies, UndecidableInstances #-} data Z = Z newtype S n = S n
然后介绍types提举。 你应该把typesI nab
读作“ n
作为参数副本,然后返回typesb
”。
class Lift nab where type I nab :: * lift :: n -> I nab -> ListF ab instance Lift Z ab where type IZ ab = b lift _ b = Cons b tooMany instance (Lift na (a -> b), I na (a -> b) ~ (a -> I nab)) => Lift (S n) ab where type I (S n) ab = a -> I nab lift (S n) f = Cons tooFew (lift nf)
下面是使用f
之前的例子,使用广义升降改写:
fF2 = lift (S (S (SZ))) f fExample4 = apply fF2 [True, True, True] fExample5 = apply fF2 [True, False] fExample6 = apply (partialApply fF2 [True, False]) [False]
不,它不能。 f
和fx
是不同的types。 由于haskell的静态types,它不能使用任何函数。 它必须采取特定types的function。
假设f
是通过typesa -> b -> c
传入的。 然后fx
有typesb -> c
。 但是a -> b -> c
必须与a -> b
具有相同的types。 因此,typesa -> (b -> c)
的函数必须是typesa -> b
的函数。 所以b
必须与b -> c
相同,这是一个无限typesb -> b -> b -> ... -> c
。 它不能存在。 (继续用b -> c
代替b
)
GHC中有一种方法可以做到这一点。 在这里和那里你需要一些types的注释来说服GHC,这一切都会解决。
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE IncoherentInstances #-} class Apply far | f -> ar where apply :: f -> [a] -> r instance Apply far => Apply (a -> f) ar where apply f (a:as) = apply (fa) as instance Apply rar where apply r _ = r test = apply ((+) :: Int -> Int -> Int) [1::Int,2] apply' :: (a -> a -> a) -> [a] -> a apply' = apply test' = apply' (+) [1,2]
这段代码很好地说明了静态和dynamictypes检查之间的区别。 通过静态types检查,编译器不能确定apply f
真的被传递了f
期望的参数,所以它拒绝了程序。 在lisp中,检查是在运行时完成的,程序可能会失败。
我不确定这会有多大的帮助,因为我正在用F#编写这个代码,但我认为这也可以在Haskell中轻松完成:
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 (zx) xs
在这种情况下,所讨论的“f”是使用允许recursion数据types定义的区别联合定义的。 这可以用来解决我提到的问题。
在一些其他人的帮助和input下,我定义了一种方法来实现这一点(以及自定义列表types),这与以前的答案有所不同。 这是一个古老的问题,但似乎仍然被访问,所以我会join完整性的方法。
我们使用一个扩展(GADT),列表types与Daniel Wagner的列表types相似,但是具有标签functiontypes而不是Peano编号。 让我们通过代码片断。 首先我们设置扩展名并定义列表types。 数据types是多态的,所以在这个公式中参数不必具有相同的types。
{-# LANGUAGE GADTs #-} -- n represents function type, o represents output type data LApp no where -- no arguments applied (function and output type are the same) End :: LApp oo -- intentional similarity to ($) (:$) :: a -> LApp mo -> LApp (a -> m) o infixr 5 :$ -- same as :
我们来定义一个函数,它可以像这样列表并将其应用到函数中。 在这里有一些types的诡计:函数具有typesn
,对listApply
的调用将仅在该types匹配列表types上的n
标签时才被编译。 通过将输出typeso
未指定,我们留下一些自由(在创build列表时,我们不必立即完全修复它可以应用的那种function)。
-- the apply function listApply :: n -> LApp no -> o listApply fun End = fun listApply fun (p :$ l) = listApply (fun p) l
而已! 我们现在可以将函数应用于存储在我们列表types中的参数。 预计更多? 🙂
-- 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
不幸的是,我们被locking在我们的列表types中,我们不能只是将普通列表转换为与listApply
一起使用。 我怀疑这是types检查器的一个根本问题(types最终取决于列表的值),但说实话我不完全确定。
-- Can't do this :( -- listApply (**) $ foldr (:$) End [2, 32]
如果您对使用异构列表感到不舒服,您只需向LApp
types添加一个额外的参数,例如:
-- Alternative definition -- data FList noa where -- Nil :: FList ooa -- Cons :: a -> FList foa -> FList (a -> f) oa
这里a
表示参数types,其中应用的函数也必须接受所有相同types的参数。
我不确定这个函数的用例。 列表是不可变的,apply会返回一个函数。 这让我想,无论你的用例是什么,应该有一个更直接的解决scheme。 但是,我可以提出一个办法如下:
instance Show (a -> b) where show a = "function" apply :: Num a => (a -> a) -> [a] -> (a -> a) apply f [] = f apply f (x:xs) = apply ((\_ -> f) (fx)) xs
这不完全是你原来的问题的答案,但我认为这可能是你的使用案例的答案。
pure f <*> [arg] <*> [arg2] ... -- example λ>pure (\abc -> (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 (\abc -> (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 (\abc -> 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"]
所以这个概念并不是一回事,但是很多这样的组合用例还是增加了一些。