Подтвердить что ты не робот

Не удается создать функцию назначения со статическим языком?

Я прочитал, что со статически типизированным языком, например 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

Это правда?

4b9b3361

Ответ 1

Полное 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)

Обратите внимание, что вы также можете применить применить себя, так как 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.

Ответ 2

Это возможно на статически типизированном языке. Вся вещь о том, как это сделать. Разумеется, использование отражения дает вам такую ​​же безопасность, как у вас с Lisp. С другой стороны, хотя я не знаю, есть ли статически типизированные языки, поддерживающие такую ​​функцию, мне кажется, что это можно сделать.

Позвольте мне показать, как цифра Scala может быть расширена для ее поддержки. Сначала рассмотрим более простой пример:

def apply[T, R](f: (T*) => R)(args: T*) = f(args: _*)

Это реальный код Scala, и он работает, но он не будет работать для любой функции, которая получает произвольные типы. Во-первых, обозначение T* вернет a 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: ([email protected]) => R)(args: [email protected]) = f(args: [email protected])

Мы больше не говорим о реальной жизни Scala, а гипотетическом улучшении. Это выглядит разумно для меня, за исключением того, что T должен быть одним типом с помощью обозначения параметра типа. Мы могли бы, возможно, просто расширить его так же:

def apply[[email protected], R](f: ([email protected]) => R)(args: [email protected]) = f(args: [email protected])

Мне кажется, что это может сработать, хотя это может быть наивностью с моей стороны.

Рассмотрим альтернативное решение, зависящее от унификации списков параметров и кортежей. Пусть, скажем, Scala имеет окончательный унифицированный список параметров и кортежи и что все кортежи были подклассом для абстрактного класса Tuple. Тогда мы могли бы написать это:

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

Там. Создание абстрактного класса Tuple будет тривиальным, а унификация списка кортежей/параметров не является надуманной идеей.

Ответ 3

Причина, по которой вы не можете сделать это на большинстве статически типизированных языков, состоит в том, что они почти все предпочитают иметь тип списка, который ограничен едиными списками. Типичная Racket - пример для языка, который может говорить о нестандартных списках (например, он имеет Listof для единообразных списков, и List для списка со статически известной длиной, которая может быть неоднородной) - но все же он назначает ограниченный тип (с равномерными списками) для Racket apply, поскольку реальный тип чрезвычайно сложно кодировать.

Ответ 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

Возможно, я перепутал 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

Ответ 5

В 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

Но это работает, только если задействованные типы ALL известны компилятору. К сожалению, добавление fundep заставляет компилятор отказаться от компиляции. Поскольку я не гукель-хэкелл, может быть, что-то еще не знает, как это исправить. К сожалению, я не знаю, как это сделать легче.

Резюме: apply не очень просто в Haskell, хотя это возможно. Думаю, вам это никогда не понадобится.

Изменить Теперь у меня есть лучшая идея, дай мне десять минут, и я представляю вам что-то без этих проблем.

Ответ 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-style и кортежем с фиксированной длиной. Поскольку большинство статических языков не представляют функции в терминах рекурсивных типов, у вас нет гибкости, чтобы делать такие вещи прозрачно. (Разумеется, макросы меняют это значение, а также поощряют разумное представление типов функций в первую очередь. Однако использование apply отрицательно влияет на производительность по очевидным причинам.)

Ответ 7

попробуйте складки. они, вероятно, похожи на то, что вы хотите. просто напишите частный случай.

haskell: foldr1 (+) [0..3] = > 6

кстати, foldr1 функционально эквивалентен foldr с аккумулятором, инициализированным как элемент списка.

есть всевозможные складки. все они технически выполняют одно и то же, хотя и по-разному, и могут делать свои аргументы в разных порядках. foldr является лишь одним из простых.

Ответ 8

В на этой странице, я прочитал, что "Применить - это как 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 в массив, чтобы сделать то же самое.

Ответ 9

Преимущество статического языка заключается в том, что это помешает вам применить функцию к аргументам неправильных типов, поэтому я считаю естественным, что это будет труднее сделать.

Учитывая список аргументов и функцию, в 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 более мощный.

Ответ 11

См. его динамическую вещь для haskell, в C, указатели функций void могут быть добавлены к другим типам, но вам нужно будет указать тип, на который он должен был быть добавлен. (Я думаю, не сделали указатели функций через некоторое время)

Ответ 12

Список в Haskell может хранить только значения одного типа, поэтому вы не могли бы делать смешные вещи, такие как (apply substring ["Foo",2,3]). Haskell также не имеет вариационных функций, поэтому (+) может принимать только два аргумента.

В Haskell есть функция $:

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

Но это действительно полезно, потому что оно имеет очень низкий приоритет или как прохождение вокруг HOF.

Я предполагаю, что вы могли бы сделать что-то вроде этого, используя типы кортежей и накопители, хотя?

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.