如何在Haskell中定义Lisp的应用?

问题描述 投票:37回答:8

不应该像Haskell这样的惰性语言允许这个定义,其中函数是curry?

apply f [] = f
apply f (x:xs) = apply (f x) xs

它基本上是一个将给定函数应用于给定参数列表的函数,并且很容易在Lisp中完成。有没有解决方法?

haskell types type-inference currying variadic-functions
8个回答
22
投票

很难给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 /存在。很想看到一个例子


11
投票

我喜欢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]

当然,如果你不喜欢单独定义lift0lift1lift2lift3等的样板,那么你需要启用一些扩展。但是如果没有他们,你可以走得很远!

以下是如何推广单个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]

10
投票

不,它不能。 ff x是不同的类型。由于haskell的静态类型性质,它不能承担任何功能。它必须采用特定类型的功能。

假设fa -> 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]


6
投票

这是在GHC中实现这一目标的一种方法。你需要在这里和那里使用一些类型的注释来说服GHC这一切都会成功。

apply f

4
投票

此代码很好地说明了静态和动态类型检查之间的差异。使用静态类型检查,编译器无法确定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中,检查在运行时完成,然后程序可能会失败。


1
投票

我不确定这会有多大帮助,因为我在F#中写这个,但我认为这也可以在Haskell中轻松完成:

others

在这种情况下,使用区分联合来定义所讨论的“f”,该联合允许递归数据类型定义。这可以用来解决我猜的上述问题。


1
投票

在一些{-# 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] 表示参数类型,其中应用的函数也必须接受所有相同类型的参数。


0
投票

这不是您原始问题的答案,但我认为这可能是您的用例的答案。

λ>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

因此,它不是同一个概念,而是很多那些组合用例,并增加了一些。

© www.soinside.com 2019 - 2024. All rights reserved.