静的言語で適用機能を作成できませんか?
-
02-10-2019 - |
質問
ScalaやHaskellのような静的にタイプされた言語でそれを読んだことがあります。リスプを作成または提供する方法はありません 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が関数を引数のリストに適用します。関数と引数のリストの両方が適用される引数です。
Applyは任意の機能を使用できます。つまり、これは任意の結果タイプと任意の引数タイプになる可能性があります。
Applyは、任意の長さ(一般的な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
次に、関数を削除する例を削除します。さまざまなもののリストからキャラクター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: _@)
私たちはもう現実のスカラについて話しているのではなく、それに対する仮説的な改善です。それを除いて、これは私には合理的に見えます 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
, 、実際のタイプをエンコードするのは非常に難しいためです。
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
OK、タイプレス:
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
タイプクラス。私が見るように、あなたは関数を探しています。これは、関数によって必要な値とまったく同じ量の値を含み、結果を返す関数を取ります。
私にとって、これは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を追加すると、コンパイラがコンピレーションを拒否します。私はHaskellの第一人者ではないので、おそらくドメーンは他の人を知っています、これを修正する方法です。悲しいことに、私はこれをより簡単に考える方法を知りません。
レスミー: apply
Haskellではそれほど簡単ではありませんが、可能です。あなたはそれを必要とすることは決してないだろうと思います。
編集 私は今より良いアイデアを持っています、私に10分を与えて、私はあなたにこれらの問題がある間に何かを提示します。
折り目を試してみてください。彼らはおそらくあなたが望むものに似ています。特別なケースを書くだけです。
Haskell: foldr1 (+) [0..3]
=> 6
ちなみに、 foldr1
機能的に同等です foldr
アキュムレータがリストの要素として初期化されています。
あらゆる種類の折り目があります。彼らはすべて技術的に同じことをしますが、さまざまな方法で行い、異なる順序で議論を行うかもしれません。 foldr
よりシンプルなものの1つです。
の上 このページ, 、「適用は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
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では、ボイド関数ポインターは他のタイプにキャストできますが、キャストするにはタイプを指定する必要があります。 (私は、しばらくの間、機能ポインターを行っていないと思います)
Haskellのリストは1つのタイプの値しか保存できないので、あなたは次のような面白いことをすることができませんでした (apply substring ["Foo",2,3])
. 。 Haskellにも変形機能はありません (+)
2つの議論しか取ることができません。
Haskellには$関数があります:
($) :: (a -> b) -> a -> b
f $ x = f x
しかし、それは非常に優先されない、またはHOFSを渡すこととして、本当に便利です。
タプルタイプと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
それはある種の「Uncrryn」のようなものだと思いますよね?
編集: :これは実際にはコンパイルされません。 @fuzxxlの答えに取って代わられます。