سؤال

لقد قرأت أنه مع لغة مكتوبة بشكل ثابت مثل 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

هل هذه حقيقة؟

هل كانت مفيدة؟

المحلول

تطبيق كامل صعب بلغة ثابتة.

في تطبيق LISP ينطبق على وظيفة على قائمة الوسيطات. كل من الوظيفة وقائمة الوسيطات هي وسيطات لتطبيقها.

  • تطبيق يمكن أن تستخدم أي وظيفة. هذا يعني أن هذا يمكن أن يكون أي نوع نتيجة وأي أنواع وسيطة.

  • التطبيق يأخذ الحجج التعسفية بطول تعسفي (في LISP المشتركة ، يتم تقييد الطول من خلال قيمة ثابتة للتنفيذ) مع أنواع تعسفية وربما مختلفة.

  • قم بتطبيق إرجاع أي نوع من القيمة التي يتم إرجاعها بواسطة الوظيفة التي حصلت عليها كوسيطة.

كيف يمكن أن تحقق نوعًا من ذلك دون تخريب نظام النوع الثابت؟

أمثلة:

(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. من ناحية أخرى ، على الرغم من أنني لا أعرف ما إذا كانت هناك لغات مكتوبة بشكل ثابت تدعم هذه الميزة ، يبدو لي أنها يمكن القيام بها.

اسمحوا لي أن أوضح كيف أظهر يمكن تمديد سكالا لدعمها. أولاً ، دعونا نرى مثالًا أبسط:

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 قد قامت أخيرًا بتوحيد قائمة المعلمات و tuples ، وأن جميع tuples كانت فئة فرعية إلى فئة مجردة Tuple. ثم يمكننا كتابة هذا:

def apply[T <: Tuple, R](f: (T) => R)(args: T) = f(args)

هناك. صنع فصل تجريدي Tuple سيكون تافها ، وتوحيد قائمة tuple/المعلمة ليس فكرة بعيدة المنال.

السبب في عدم قدرتك على فعل ذلك في اللغات التي كتبت بشكل ثابت هو أنها تختار جميعها تقريبًا الحصول على نوع قائمة يقتصر على قوائم موحدة. المضرب المكتوبة هو مثال على لغة يمكن أن تتحدث عن القوائم التي لم يتم كتابتها بشكل موحد (على سبيل المثال ، لديها أ Listof للحصول على قوائم موحدة ، و List للحصول على قائمة ذات طول معروف بشكل ثابت يمكن أن يكون غير موحدة)-ولكن لا يزال يعين نوعًا محدودًا (مع قوائم موحدة) للمضرب apply, ، لأن النوع الحقيقي يصعب تشفيره.

إنه تافه في سكالا:

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

من الممكن الكتابة 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

ووظائف variadic مثل هذا:

def sum (xs: Int*): Int = xs.foldLeft(0)(_ + _)

كن هذا:

def sum (t: TypeList[Accept[Int]]): Int = t.xs.foldLeft(0)(_ + _)

المشكلة الوحيدة في كل هذا هي أنه في Scala (وفي معظم اللغات الساكنة الأخرى) ، ليست الأنواع من الدرجة الأولى بما يكفي لتحديد التشاهك بين أي بنية على غرار Cont و Tuple ذات الطول الثابت. نظرًا لأن معظم اللغات الثابتة لا تمثل وظائف من حيث الأنواع المتكررة ، فلا تتمتع بالمرونة في القيام بأشياء مثل هذا بشفافية. (سوف تغير وحدات الماكرو هذا ، بالطبع ، بالإضافة إلى تشجيع تمثيل معقول لأنواع الوظائف في المقام الأول. ومع ذلك ، باستخدام apply يؤثر سلبا على الأداء لأسباب واضحة.)

في Haskell ، لا توجد نوع بيانات لقوائم متعددة الأنواع ، على الرغم من أنني أعتقد ، أنه يمكنك اختراق شيء مثل هذا معًا بشكل غامض Typeable typeclass. كما أرى ، أنت تبحث عن وظيفة ، والتي تأخذ وظيفة ، والتي تحتوي على نفس مقدار القيم بالضبط حسب الحاجة من الوظيفة وإرجاع النتيجة.

بالنسبة لي ، هذا يبدو مألوفًا جدًا لـ Haskells uncurryالوظيفة ، فقط أن الأمر يتطلب tuple بدلاً من القائمة. الفرق هو ، أن tuple لديه دائما نفس عدد العناصر (هكذا (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 ليس من السهل جدًا في هاسكل ، على الرغم من أنه ممكن. أعتقد أنك لن تحتاج إليها أبدًا.

يحرر لدي فكرة أفضل الآن ، أعطني عشر دقائق وأقدم لك شيئًا ما في هذه المشكلات.

جرب الطيات. ربما تشبه ما تريد. فقط اكتب حالة خاصة منه.

هاسكل: foldr1 (+) [0..3] => 6

صدفة، foldr1 تعادل وظيفيا ل foldr مع تهيئة المجمع كعنصر القائمة.

هناك كل أنواع الطيات. إنهم جميعًا يفعلون نفس الشيء تقنيًا ، على الرغم من طرق مختلفة ، وقد يقومون بحججهم بأوامر مختلفة. foldr هو مجرد واحد من أبسط.

على هذه الصفحة, ، قرأت أن "التطبيق يشبه فونكال تمامًا ، باستثناء أن حجتها النهائية يجب أن تكون قائمة ؛ يتم التعامل مع عناصر تلك القائمة كما لو كانت حجج إضافية إلى funcall."

في Scala ، يمكن أن يكون للوظائف varargs (الحجج المتنوعة) ، مثل الإصدارات الأحدث من 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 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. لكني أظن ذلك 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 تخزين قيم فقط من نوع واحد ، لذلك لا يمكنك فعل أشياء مضحكة مثل (apply substring ["Foo",2,3]). كما أنه لا يوجد لديه وظائف متنوعة ، لذلك (+) لا يمكن أن تأخذ سوى حجتين.

هناك وظيفة $ في Haskell:

($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

لكن هذا مفيد حقًا لأنه ذو أسبقية منخفضة للغاية ، أو كما يمر HOFs.

أتصور أنك قد تكون قادرًا على فعل شيء مثل هذا باستخدام أنواع Tuple و 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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top