繁体   English   中英

无法使用静态语言创建应用功能?

[英]Cannot create apply function with static language?

我已经阅读过像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

这是真的吗?

在静态类型语言中完全可能。 整个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)

那仍然在使用Scala,但是我们通过说它必须接收一个HList而不是任意数量的参数来对f施加一个很大的限制。 假设我们使用@来进行从异构参数到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)

请注意,您也可以申请自己申请,因为申请是一项功能。

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

也许我混淆了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

在Haskell中,没有多类型列表的数据类型,尽管我相信,你可以使用神秘的Typeable类型类一起破解这样的东西。 正如我所看到的,你正在寻找一个函数,它接受一个函数,它包含函数所需的完全相同的值,并返回结果。

对我来说,这看起来非常熟悉haskells uncurry函数,只需要一个元组而不是列表。 区别在于,元组总是具有相同的元素数(因此(1,2)(1,2,3)具有不同类型(!)),并且内容可以是任意类型的。

uncurry函数有这个定义:

uncurry :: (a -> b -> c) -> (a,b) -> c
uncurry f (a,b) = f a b

你需要的是某种不成熟的,以提供任意数量的参数的方式重载。 我想到这样的事情:

{-# 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会导致编译器拒绝编译。 由于我不是哈克尔大师,也许其他人知道,如何解决这个问题。 可悲的是,我不知道如何更容易地实现这一目标。

Résumee:虽然可能,但在Haskell中apply并不容易。 我想,你永远不需要它。

编辑我现在有一个更好的主意,给我十分钟,我给你一些没有这些问题的东西。

只要以特定方式键入函数,就可以用静态类型语言编写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-style结构和固定长度元组之间的同构。 因为大多数静态语言不表示递归类型的函数,所以您无法灵活地透明地执行此类操作。 (宏当然会改变这一点,并且首先鼓励合理地表示函数类型。但是,使用apply会对性能产生负面影响,原因很明显。)

尝试折叠。 它们可能与你想要的相似。 只写一个特例。

haskell: foldr1 (+) [0..3] => 6

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

有各种各样的折叠。 他们在技术上做同样的事情,虽然以不同的方式,并可能以不同的顺序做他们的论点。 foldr只是其中一个更简单的。

这个页面上 ,我读到“Apply就像funcall,除了它的最终参数应该是一个列表;该列表的元素被视为它们是funcall的附加参数。”

在Scala中,函数可以有varargs (可变参数),就像Java的新版本一样。 您可以使用符号将列表(或任何Iterable对象)转换为更多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中存在概念。 但我怀疑在lisp中apply更强大。

对于Haskell,要动态执行,请参阅Data.Dynamic,特别是dynApp: http//www.haskell.org/ghc/docs/6.12.1/html/libraries/base/Data-Dynamic.html

看看他对于haskell的动态,在C中,void函数指针可以被转换为其他类型,但你必须指定要将其强制转换的类型。 (我想,有一段时间没有做过函数指针)

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

我猜这是一种'不发生',不是吗?

编辑 :这实际上并没有编译; 被@ FUZxxl的答案所取代。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM