Не можете создать функцию применения со статическим языком? - PullRequest
21 голосов
/ 12 сентября 2010

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

Это правда?

Ответы [ 12 ]

10 голосов
/ 12 сентября 2010

Это вполне возможно на статически типизированном языке.Вся эта штука об этом.Конечно, использование отражения дает вам такую ​​же безопасность типов, как и в 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, но мы накладываем большое ограничение на f, говоря, что он должен получить HList вместо произвольного количества параметров.Допустим, мы используем @ для преобразования разнородных параметров в HList, аналогично * преобразует из однородных параметров в Seq:

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

Мы не говорим о реальныхжизнь Скала больше, но гипотетическое улучшение к нему.Это выглядит разумно для меня, за исключением того, что 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 было бы тривиальным, и объединение списка кортежей / параметров не является надуманной идеей.

9 голосов
/ 12 сентября 2010

Полный 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, так как 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

Один или оба из вышеперечисленных должны быть выполнены в типичной проверке статического типаязык программирования.Ни один из них не даст вам полностью статически проверенную и полностью гибкую заявку.

8 голосов
/ 12 сентября 2010

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

4 голосов
/ 12 сентября 2010

Это просто в 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
2 голосов
/ 13 сентября 2010

Можно написать 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 отрицательно влияет на производительность по очевидным причинам.)

2 голосов
/ 12 сентября 2010

В 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, хотя возможно.Я думаю, вам это никогда не понадобится.

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

1 голос
/ 12 сентября 2010

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

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

1 голос
/ 12 сентября 2010

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

1 голос
/ 12 сентября 2010

попробуйте фолды. они, вероятно, похожи на то, что вы хотите. просто напишите специальный случай этого.

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

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

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

0 голосов
/ 16 сентября 2010

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

...