无法使用静态语言创建 apply 函数?
-
02-10-2019 - |
题
我读过,使用像 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
也许我混在一起 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
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中,元组可以最好地捕获数据,因为它可以存储不同类型的值。考虑到这一点 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
我认为,如果您认为将一类功能应用于其参数的机制,那么该概念就在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的答案取代。