簡體   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