我已经读过,使用 Scala 或 Haskell 等静态类型语言无法创建或提供 Lispapply
函数:
(apply #'+ (list 1 2 3)) => 6
或者可能
(apply #'list '(list :foo 1 2 "bar")) => (:FOO 1 2 "bar")
(apply #'nth (list 1 '(1 2 3))) => 2
这是事实吗?
我已经读过,使用 Scala 或 Haskell 等静态类型语言无法创建或提供 Lispapply
函数:
(apply #'+ (list 1 2 3)) => 6
或者可能
(apply #'list '(list :foo 1 2 "bar")) => (:FOO 1 2 "bar")
(apply #'nth (list 1 '(1 2 3))) => 2
这是事实吗?
这在静态类型语言中是完全可能的。整个java.lang.reflect
事情就是这样做。当然,使用反射可以为您提供与使用 Lisp 一样多的类型安全性。另一方面,虽然我不知道是否有支持这种功能的静态类型语言,但在我看来是可以做到的。
让我展示一下我如何扩展 Scala 以支持它。首先,让我们看一个更简单的例子:
def apply[T, R](f: (T*) => R)(args: T*) = f(args: _*)
这是真正的 Scala 代码,它可以工作,但它不适用于任何接收任意类型的函数。一方面,符号T*
将返回 a Seq[T]
,这是一个本地类型的序列。但是,存在异构类型的序列,例如HList。
所以,首先,让我们在这里尝试使用HList
:
def apply[T <: HList, R](f: (T) => R)(args: T) = f(args)
f
这仍然适用于 Scala,但我们通过说它必须接收一个HList
,而不是任意数量的参数来施加很大的限制。假设我们使用@
从异构参数到 的转换HList
,同样的方式*
从同构参数转换到Seq
:
def apply[T, R](f: (T@) => R)(args: T@) = f(args: _@)
我们不再谈论现实生活中的 Scala,而是对其进行假设性改进。这在我看来是合理的,除了T
按类型参数表示法应该是一种类型。也许,我们可以用同样的方式扩展它:
def apply[T@, R](f: (T@) => R)(args: T@) = f(args: _@)
对我来说,这似乎可行,尽管这可能是我的幼稚。
让我们考虑另一种解决方案,一种取决于参数列表和元组的统一。假设 Scala 终于统一了参数列表和元组,并且所有元组都是抽象类的子类Tuple
。然后我们可以这样写:
def apply[T <: Tuple, R](f: (T) => R)(args: T) = f(args)
那里。制作一个抽象类Tuple
将是微不足道的,元组/参数列表统一并不是一个牵强的想法。
在静态语言中,完整的 APPLY 是很困难的。
在 Lisp 中 APPLY 将函数应用于参数列表。函数和参数列表都是 APPLY 的参数。
APPLY 可以使用任何功能。这意味着这可以是任何结果类型和任何参数类型。
APPLY 采用任意长度的任意参数(在 Common Lisp 中,长度受特定于实现的常量值限制),具有任意且可能不同的类型。
APPLY 返回它作为参数获得的函数返回的任何类型的值。
一种类型如何在不破坏静态类型系统的情况下进行检查?
例子:
(apply #'+ '(1 1.4)) ; the result is a float.
(apply #'open (list "/tmp/foo" :direction :input))
; the result is an I/O stream
(apply #'open (list name :direction direction))
; the result is also an I/O stream
(apply some-function some-arguments)
; the result is whatever the function bound to some-function returns
(apply (read) (read))
; neither the actual function nor the arguments are known before runtime.
; READ can return anything
交互示例:
CL-USER 49 > (apply (READ) (READ)) ; call APPLY
open ; enter the symbol OPEN
("/tmp/foo" :direction :input :if-does-not-exist :create) ; enter a list
#<STREAM::LATIN-1-FILE-STREAM /tmp/foo> ; the result
现在以函数 REMOVE 为例。我们将从不同事物的列表中删除字符 a。
CL-USER 50 > (apply (READ) (READ))
remove
(#\a (1 "a" #\a 12.3 :foo))
(1 "a" 12.3 :FOO)
请注意,您也可以应用 apply 本身,因为 apply 是一个函数。
CL-USER 56 > (apply #'apply '(+ (1 2 3)))
6
还有一点复杂,因为函数 APPLY 接受任意数量的参数,其中只有最后一个参数需要是一个列表:
CL-USER 57 > (apply #'open
"/tmp/foo1"
:direction
:input
'(:if-does-not-exist :create))
#<STREAM::LATIN-1-FILE-STREAM /tmp/foo1>
如何处理?
放宽静态类型检查规则
限制申请
上述一项或两项都必须使用典型的静态类型检查编程语言来完成。两者都不会给你一个完全静态检查和完全灵活的应用程序。
在大多数静态类型语言中你不能这样做的原因是它们几乎都选择了一个仅限于统一列表的列表类型。 Typed Racket是一种可以讨论非统一类型列表的语言的示例(例如,它有一个Listof
用于统一列表和List
一个静态已知长度的列表可以是非统一的)——但它仍然分配Racket 的有限类型(具有统一列表)apply
,因为真实类型极难编码。
这在 Scala 中是微不足道的:
Welcome to Scala version 2.8.0.final ...
scala> val li1 = List(1, 2, 3)
li1: List[Int] = List(1, 2, 3)
scala> li1.reduceLeft(_ + _)
res1: Int = 6
scala> def m1(args: Any*): Any = args.length
m1: (args: Any*)Any
scala> val f1 = m1 _
f1: (Any*) => Any = <function1>
scala> def apply(f: (Any*) => Any, args: Any*) = f(args: _*)
apply: (f: (Any*) => Any,args: Any*)Any
scala> apply(f1, "we", "don't", "need", "no", "stinkin'", "types")
res0: Any = 6
funcall
,apply
所以:scala> def funcall(f: (Any*) => Any, args: Any*) = f(args: _*)
funcall: (f: (Any*) => Any,args: Any*)Any
scala> def apply(f: (Any*) => Any, args: List[Any]) = f(args: _*)
apply: (f: (Any*) => Any,args: List[Any])Any
scala> apply(f1, List("we", "don't", "need", "no", "stinkin'", "types"))
res0: Any = 6
scala> funcall(f1, "we", "don't", "need", "no", "stinkin'", "types")
res1: Any = 6
apply
只要函数以特定方式键入,就可以用静态类型语言编写。在大多数语言中,函数都有单独的参数,要么由拒绝(即没有可变参数调用)或类型化的接受(即可变参数调用可能,但仅当所有其他参数都是类型 T 时)终止。以下是在 Scala 中建模的方法:
trait TypeList[T]
case object Reject extends TypeList[Reject]
case class Accept[T](xs: List[T]) extends TypeList[Accept[T]]
case class Cons[T, U](head: T, tail: U) extends TypeList[Cons[T, U]]
请注意,这不会强制格式正确(尽管我相信确实存在类型界限),但您明白了。然后你apply
定义了这样的:
apply[T, U]: (TypeList[T], (T => U)) => U
然后,您的函数是根据类型列表事物定义的:
def f (x: Int, y: Int): Int = x + y
变成:
def f (t: TypeList[Cons[Int, Cons[Int, Reject]]]): Int = t.head + t.tail.head
像这样的可变参数函数:
def sum (xs: Int*): Int = xs.foldLeft(0)(_ + _)
变成这样:
def sum (t: TypeList[Accept[Int]]): Int = t.xs.foldLeft(0)(_ + _)
所有这一切的唯一问题是,在 Scala(以及大多数其他静态语言)中,类型不足以定义任何 cons 样式结构和固定长度元组之间的同构。因为大多数静态语言不代表递归类型的函数,所以你没有灵活性来透明地做这样的事情。(当然,宏会改变这一点,并首先鼓励对函数类型进行合理的表示。但是,使用apply
明显的原因会对性能产生负面影响。)
在 Haskell 中,没有用于多类型列表的数据类型,尽管我相信,你可以用神秘的类型类来破解类似的东西Typeable
。如我所见,您正在寻找一个函数,该函数接受一个函数,该函数包含与函数所需数量完全相同的值并返回结果。
对我来说,这对 haskellsuncurry
函数看起来很熟悉,只是它需要一个元组而不是一个列表。不同之处在于,元组始终具有相同数量的元素(因此(1,2)
和(1,2,3)
具有不同类型(!)),并且内容可以是任意类型的。
该uncurry
函数具有以下定义:
uncurry :: (a -> b -> c) -> (a,b) -> c
uncurry f (a,b) = f a b
您需要的是某种 uncurry,它以某种方式重载以提供任意数量的参数。我想到了这样的事情:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
class MyApply f t r where
myApply :: f -> t -> r
instance MyApply (a -> b -> c) (a,b) c where
myApply f (a,b) = f a b
instance MyApply (a -> b -> c -> d) (a,b,c) d where
myApply f (a,b,c) = f a b c
-- and so on
但这只有在编译器知道所有涉及的类型时才有效。可悲的是,添加 fundep 会导致编译器拒绝编译。由于我不是haskell 大师,也许其他人知道如何解决这个问题。可悲的是,我不知道如何更容易地归档它。
简历: apply
虽然有可能,但在 Haskell 中并不是很容易。我想,你永远不需要它。
编辑我现在有一个更好的主意,给我十分钟,我给你一些没有这些问题的东西。
在这个页面上,我读到“Apply 就像 funcall,只是它的最后一个参数应该是一个列表;该列表的元素被视为是 funcall 的附加参数。”
在 Scala 中,函数可以具有可变参数(可变参数),就像 Java 的较新版本一样。:_*
您可以使用符号示例将列表(或任何 Iterable 对象)转换为更多可变参数:
//The asterisk after the type signifies variadic arguments
def someFunctionWithVarargs(varargs: Int*) = //blah blah blah...
val list = List(1, 2, 3, 4)
someFunctionWithVarargs(list:_*)
//equivalent to
someFunctionWithVarargs(1, 2, 3, 4)
事实上,即使是 Java 也可以做到这一点。Java varargs 可以作为参数序列或数组传递。您所要做的就是将您的 Java 转换List
为一个数组来做同样的事情。
尝试折叠。它们可能与您想要的相似。只写一个特例。
哈斯克尔:foldr1 (+) [0..3]
=> 6
顺便说一句,foldr1
在功能上等同于foldr
将累加器初始化为列表的元素。
有各种各样的褶皱。从技术上讲,他们都在做同样的事情,尽管方式不同,并且可能以不同的顺序进行论证。foldr
只是较简单的一种。
静态语言的好处是它会阻止您将函数应用于不正确类型的参数,所以我认为它会更难做是很自然的。
给定参数列表和函数,在 Scala 中,元组将最好地捕获数据,因为它可以存储不同类型的值。考虑到这一点,与以下tupled
内容有些相似apply
:
scala> val args = (1, "a")
args: (Int, java.lang.String) = (1,a)
scala> val f = (i:Int, s:String) => s + i
f: (Int, String) => java.lang.String = <function2>
scala> f.tupled(args)
res0: java.lang.String = a1
对于一个参数的函数,实际上有apply
:
scala> val g = (i:Int) => i + 1
g: (Int) => Int = <function1>
scala> g.apply(2)
res11: Int = 3
我认为,如果您认为 apply 与将一流函数应用于其参数的机制一样,那么这个概念在 Scala 中就存在。但我怀疑apply
在 lisp 中更强大。
请参阅他对 haskell 的动态内容,在 C 中,void 函数指针可以转换为其他类型,但您必须指定将其转换为的类型。(我想,有一段时间没有做函数指针了)
对于 Haskell,要动态执行,请参阅 Data.Dynamic,尤其是 dynApp:http ://www.haskell.org/ghc/docs/6.12.1/html/libraries/base/Data-Dynamic.html
Haskell 中的列表只能存储一种类型的值,所以你不能做一些有趣的事情,比如(apply substring ["Foo",2,3])
. Haskell 也没有可变参数函数,因此(+)
只能接受两个参数。
Haskell 中有一个 $ 函数:
($) :: (a -> b) -> a -> b
f $ x = f x
但这只是真正有用的,因为它的优先级非常低,或者作为传递 HOF。
我想你也许可以使用元组类型和fundeps来做这样的事情?
class Apply f tt vt | f -> tt, f -> vt where
apply :: f -> tt -> vt
instance Apply (a -> r) a r where
apply f t = f t
instance Apply (a1 -> a2 -> r) (a1,a2) r where
apply f (t1,t2) = f t1 t2
instance Apply (a1 -> a2 -> a3 -> r) (a1,a2,a3) r where
apply f (t1,t2,t3) = f t1 t2 t3
我想那是一种“uncurryN”,不是吗?
编辑:这实际上并没有编译;被@FUZxxl 的回答所取代。