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

时间:2010-09-12 02:03:22

标签: scala programming-languages haskell lisp static-typing

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

这是真的吗?

12 个答案:

答案 0 :(得分:10)

完全可以使用静态类型语言。整个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,但我们对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将是微不足道的,而元组/参数列表统一并不是一个牵强附会的想法。

答案 1 :(得分:9)

完整的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>

如何处理?

  • 放松静态类型检查规则

  • 限制申请

上述一个或两个必须以典型的静态类型检查编程语言完成。两者都不会给你一个完全静态检查和完全灵活的应用程序。

答案 2 :(得分:8)

在大多数静态类型语言中你不能这样做的原因是它们几乎都选择了一个仅限于统一列表的列表类型。 Typed Racket是一种语言示例,可以讨论未统一输入的列表(例如,统一列表有Listof,而静态长度列表有List这可能是非均匀的) - 但它仍然为Racket apply指定一个有限类型(带有统一列表),因为实际类型很难编码。

答案 3 :(得分:4)

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

答案 4 :(得分:2)

在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: apply在Haskell中并不容易,尽管可能。我想,你永远不需要它。

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

答案 5 :(得分:2)

只要以特定方式键入函数,就可以用静态类型语言编写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会因显而易见的原因而对性能产生负面影响。)

答案 6 :(得分:1)

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

haskell:foldr1 (+) [0..3] =&gt; 6

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

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

答案 7 :(得分:1)

this page上,我读到“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转换为数组以执行相同的操作。

答案 8 :(得分:1)

静态语言的好处是它会阻止你将函数应用于不正确类型的参数,所以我认为它很难做到。

给定一个参数列表和一个函数,在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中存在概念。但我怀疑在lisp中apply更强大。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

答案 11 :(得分:-1)

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