apply
ScalaやHaskellのような静的に型付けされた言語では、Lisp関数を作成または提供する方法がないことを読みました。
(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
これは真実ですか?
apply
ScalaやHaskellのような静的に型付けされた言語では、Lisp関数を作成または提供する方法がないことを読みました。
(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*
返します。ただし、 HListSeq[T]
などの異種タイプのシーケンスがあります。
それで、最初に、ここで使用してみましょう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
これは、タイプパラメータ表記によって1つのタイプであると想定されていることを除いて、私には合理的に見えます。おそらく、同じ方法で拡張することもできます。
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
実際のタイプはエンコードが非常に難しいため、Racketの限定タイプ(統一リスト付き) 。
静的言語では、完全なAPPLYは困難です。
Lispでは、APPLYは引数のリストに関数を適用します。関数と引数のリストはどちらもAPPLYの引数です。
APPLYは任意の関数を使用できます。つまり、これは任意の結果タイプと任意の引数タイプである可能性があります。
APPLYは、任意の、場合によっては異なるタイプの任意の長さの任意の引数を取ります(Common Lispでは、長さは実装固有の定数値によって制限されます)。
APPLYは、引数として取得した関数によって返される任意のタイプの値を返します。
静的型システムを破壊することなく、どのように1つの型がそれをチェックするでしょうか?
例:
(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)
applyは関数であるため、apply自体を適用することもできます。
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>
どのように対処するのですか?
静的型チェックルールを緩和する
APPLYを制限する
上記の一方または両方は、一般的な静的型チェックプログラミング言語で実行する必要があります。どちらも、完全に静的にチェックされ、完全に柔軟な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
Haskellには、マルチタイプリストのデータ型はありませんが、このようなものを不思議な型クラスで一緒にハックできると私は信じていますTypeable
。私が見ているように、あなたは関数を取り、関数が必要とするのとまったく同じ量の値を含み、結果を返す関数を探しています。
私にとって、これはhaskellsuncurry
関数に非常に馴染みがあるように見えますが、リストの代わりにタプルを使用するだけです。違いは、タプルの要素数は常に同じであり(したがって(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を追加すると、コンパイラはコンパイルを拒否します。私はHaskellの第一人者ではないので、おそらく他のドメオネがこれを修正する方法を知っています。悲しいことに、これを簡単にアーカイブする方法がわかりません。
Résumee: apply
可能ではありますが、Haskellではそれほど簡単ではありません。私は、あなたがそれを必要とすることは決してないだろうと思います。
編集私は今より良い考えを持っています、私に10分を与えてください、そして私はあなたにこれらの問題なしで何かを提示します。
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(および他のほとんどの静的言語)では、型が、コンススタイルの構造と固定長のタプルの間の同型を定義するのに十分なファーストクラスではないことです。ほとんどの静的言語は再帰型の観点から関数を表していないため、このようなことを透過的に行う柔軟性がありません。(もちろん、マクロはこれを変更し、そもそも関数型の合理的な表現を奨励します。ただし、使用するapply
と、明らかな理由でパフォーマンスに悪影響を及ぼします。)
フォールドを試してください。彼らはおそらくあなたが望むものに似ています。特別なケースを書いてください。
haskell:foldr1 (+) [0..3]
=> 6
ちなみに、リストの要素として初期化されたアキュムレータfoldr1
と機能的に同等です。foldr
あらゆる種類の折り目があります。それらはすべて、技術的には同じことを行いますが、方法は異なり、引数を異なる順序で行う可能性があります。foldr
単純なものの1つにすぎません。
このページで、「適用は、最後の引数がリストである必要があることを除いて、funcallと同じです。そのリストの要素は、funcallへの追加の引数であるかのように扱われます。」と読みました。
Scalaでは、新しいバージョンのJavaのように、関数はvarargs(可変引数)を持つことができます。:_*
表記例を使用して、リスト(または任意の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は、引数のシーケンスまたは配列として渡すことができます。あなたがしなければならないList
のは、同じことをするためにあなたのJavaを配列に変換することだけです。
静的言語の利点は、間違った型の引数に関数を適用できないことです。そのため、実行が難しくなるのは当然だと思います。
引数と関数のリストが与えられた場合、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
1つの引数の関数については、実際には次のようになり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
彼のhaskellの動的なことを参照してください。Cでは、void関数ポインターを他の型にキャストできますが、キャスト先の型を指定する必要があります。(私は、しばらくの間、関数ポインタを実行していないと思います)
Haskellのリストは1つのタイプの値しか保存できないので、のような面白いことはできません(apply substring ["Foo",2,3])
。Haskellには可変個引数関数もないので、(+)
2つの引数しかとることができません。
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
それは一種の「uncurryN」だと思いますね。
編集:これは実際にはコンパイルされません。@FUZxxlの回答に取って代わられました。