我读过,使用像 Scala 或 Haskell 这样的静态类型语言,无法创建或提供 Lisp apply 功能:

(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

这是真的吗?

有帮助吗?

解决方案

在静态语言中,完全适用。

在LISP中,适用于参数列表。函数和参数列表都是要应用的参数。

  • 应用可以使用任何功能。这意味着这可能是任何结果类型和任何参数类型。

  • 申请以任意长度为任意长度的任意论点(通常是由实现特定常数值)的任意类型和可能不同的类型的限制。

  • 申请返回其作为参数所获得的函数返回的任何类型的值。

一种类型如何在不颠覆静态类型系统的情况下进行检查?

例子:

(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

现在,删除功能的示例。我们将从不同事物列表中删除字符A。

CL-USER 50 > (apply (READ) (READ))
remove
(#\a (1 "a" #\a 12.3 :foo))
(1 "a" 12.3 :FOO)

请注意,您还可以应用自身应用,因为应用是一个函数。

CL-USER 56 > (apply #'apply '(+ (1 2 3)))
6

也有轻微的并发症,因为函数应用程序需要任意数量的参数,其中只有最后一个参数才能是列表:

CL-USER 57 > (apply #'open
                    "/tmp/foo1"
                    :direction
                    :input
                    '(:if-does-not-exist :create))
#<STREAM::LATIN-1-FILE-STREAM /tmp/foo1>

如何处理?

  • 放松静态类型检查规则

  • 限制适用

上述一个或两个必须以典型的静态型检查编程语言进行。这两个都不会为您提供全静态检查并完全灵活地应用。

其他提示

在静态类型的语言中,这是完全可能的。整体 java.lang.reflect 事情是关于这样做的。当然,使用反射为您提供与LISP一样多的安全性。另一方面,虽然我不知道是否有静态打字的语言支持该功能,但在我看来,这可以做到。

让我展示如何扩展Scala以支持它。首先,让我们看看一个更简单的示例:

def apply[T, R](f: (T*) => R)(args: T*) = f(args: _*)

这是真正的Scala代码,它可以正常工作,但是它对接收任意类型的任何功能都无法使用。一方面,符号 T* 将返回 Seq[T], ,这是一个自制的序列。但是,存在异体序列的序列,例如 hlist.

所以,首先,让我们尝试使用 HList 这里:

def apply[T <: HList, R](f: (T) => R)(args: T) = f(args)

那仍在工作,但我们对 f 通过说必须收到 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 将是微不足道的,元组/参数列表统一并不是一个牵强的想法。

您之所以不能在大多数静态类型的语言中这样做的原因是,它们几乎所有人都选择具有仅限于统一列表的列表类型。 打字球拍 是一种可以谈论不统一键入列表的语言的示例(例如,它有一个 Listof 对于统一清单,以及 List 对于具有静态已知长度的列表,可能是不均匀的) - 但仍然为球拍分配有限的类型(均匀列表) 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

也许我混在一起 funcallapply, , 所以:

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 Typeclass。如我所见,您正在寻找一个函数,该函数具有一个函数,其中包含与功能所需的值完全相同的值并返回结果。

对我来说,这看起来很熟悉 uncurry功能,只是需要一个元组而不是列表。区别在于,元组总是具有相同的元素计数(所以 (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 Guru,也许还有Domeone知道,如何解决这个问题。可悲的是,我不知道该如何更轻松地构造。

Résumee: apply 在哈斯克尔(Haskell)中不是很容易的。我想,您永远不需要它。

编辑 我现在有一个更好的主意,给我十分钟,我向您介绍了这些问题。

尝试折叠。它们可能类似于您想要的。只需写一个特殊情况即可。

哈斯克尔: foldr1 (+) [0..3] => 6

顺便, foldr1 在功能上等同于 foldr 将累加器初始化为列表的元素。

有各种各样的折叠。他们在技术上都做同样的事情,尽管以不同的方式进行,并且可能会以不同的命令进行论点。 foldr 只是简单的之一。

这一页, ,我读到“申请就像funcall一样,除了它的最终论点应该是列表;该列表的元素被视为将其视为对Funcall的其他论点。”

在Scala中,功能可以具有 varargs (variadic参数),例如Java的较新版本。您可以使用符号将列表(或任何可观的对象)转换为更多vararg参数 :_* 例子:

//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 阵列做同样的事情。

静态语言的好处是,它会阻止您将功能应用于不正确类型的论点,因此我认为很难做到这一点很自然。

给定参数列表和函数,在Scala中,元组可以最好地捕获数据,因为它可以存储不同类型的值。考虑到这一点 tupledapply:

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

我认为,如果您认为将一类功能应用于其参数的机制,那么该概念就在Scala中。但我怀疑 apply 在LISP中更强大。

对于Haskell,动态执行此操作,请参阅data.Dynamic,尤其是DynApp: http://www.haskell.org/ghc/docs/6.12.1/html/libraries/base/data-dynamic.html

在C中看到他对Haskell的动态事物,可以将Void函数指针施加到其他类型上,但是您必须指定将其施加到的类型。 (我认为,一段时间没有完成功能指针)

Haskell中的列表只能存储一种类型的值,因此您不能做有趣的事情 (apply substring ["Foo",2,3]). 。 haskell也没有变异功能,所以 (+) 只能有两个论点。

Haskell中有一个$函数:

($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

但这仅是非常有用的,因为它的优先级非常低,或者是在HOF周围传递的。

我想您可能可以使用元组类型和Fundep做这样的事情吗?

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的答案取代。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top