tutorial software parte ejemplos desventajas curso comentarios scala programming-languages haskell lisp static-typing

scala - software - ¿No se puede crear la función de aplicar con lenguaje estático?



scala software (12)

El beneficio de un lenguaje estático es que le impediría aplicar una función a los argumentos de tipos incorrectos, por lo que creo que es natural que sea más difícil de hacer.

Dada una lista de argumentos y una función, en Scala, una tupla capturaría mejor los datos, ya que puede almacenar valores de diferentes tipos. Con eso en mente, tupled tiene cierta semejanza con la 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

Para la función de un argumento, en realidad se apply :

scala> val g = (i:Int) => i + 1 g: (Int) => Int = <function1> scala> g.apply(2) res11: Int = 3

Creo que si piensa que se aplica como el mecanismo para aplicar una función de primera clase a sus argumentos, entonces el concepto está ahí en Scala. Pero sospecho que apply en lisp es más potente.

He leído que con un lenguaje de tipo estático como Scala o Haskell no hay manera de crear o proporcionar una función de apply Lisp:

(apply #''+ (list 1 2 3)) => 6

o tal vez

(apply #''list ''(list :foo 1 2 "bar")) => (:FOO 1 2 "bar") (apply #''nth (list 1 ''(1 2 3))) => 2

¿Es esto una verdad?


En esta página , leí que "Aplicar es como funcall, excepto que su argumento final debería ser una lista; los elementos de esa lista se tratan como si fueran argumentos adicionales a una funcall".

En Scala, las funciones pueden tener varargs (argumentos variadic), como las versiones más nuevas de Java. Puede convertir una lista (o cualquier objeto iterable) en más parámetros vararg usando la notación :_* Ejemplo:

//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)

De hecho, incluso Java puede hacer esto. Java varargs se puede pasar como una secuencia de argumentos o como una matriz. Todo lo que tendrías que hacer es convertir tu List Java en una matriz para hacer lo mismo.


En Haskell, no hay un tipo de datos para las listas de varios tipos, aunque creo que se puede hackear algo como esto junto con la misteriosa Typeclass. Como veo, está buscando una función, que toma una función, que contiene exactamente la misma cantidad de valores que la función necesita y devuelve el resultado.

Para mí, esto parece muy familiar para la función de haskells uncurry , solo que se necesita una tupla en lugar de una lista. La diferencia es que una tupla tiene siempre el mismo número de elementos (por lo que (1,2) y (1,2,3) son de diferentes tipos (!)) Y los contenidos pueden ser escritos de forma arbitraria.

La función uncurry tiene esta definición:

uncurry :: (a -> b -> c) -> (a,b) -> c uncurry f (a,b) = f a b

Lo que necesita es algún tipo de aplastamiento que esté sobrecargado de manera que proporcione un número arbitrario de parámetros. Pienso en algo como esto:

{-# 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

Pero esto solo funciona, si el compilador conoce TODOS los tipos involucrados. Lamentablemente, agregar un fundep hace que el compilador rechace la compilación. Como no soy un gurú de haskell, tal vez alguien más lo sepa, cómo solucionarlo. Lamentablemente, no sé cómo archivar esto más fácil.

Currículum: apply no es muy fácil en Haskell, aunque es posible. Supongo que nunca lo necesitarás.

Editar Tengo una mejor idea ahora, dame diez minutos y te presento algo sin estos problemas.


Es perfectamente posible en un lenguaje estáticamente tipado. Todo el asunto de java.lang.reflect se trata de hacer eso. Por supuesto, el uso de la reflexión le proporciona tanta seguridad de tipos como con Lisp. Por otro lado, aunque no sé si hay lenguajes con tipos estáticos que admitan tal característica, me parece que podría hacerse.

Déjame mostrarte cómo creo que Scala podría extenderse para apoyarlo. Primero, veamos un ejemplo más simple:

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

Este es el código real de Scala, y funciona, pero no funcionará para ninguna función que reciba tipos arbitrarios. Por un lado, la notación T* devolverá una Seq[T] , que es una secuencia tipificada de forma homogénea. Sin embargo, hay secuencias de tipo heterogéneo, como la lista HList .

Entonces, primero, intentemos usar HList aquí:

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

Eso sigue funcionando en Scala, pero ponemos una gran restricción en f al decir que debe recibir una HList , en lugar de un número arbitrario de parámetros. Digamos que usamos @ para hacer la conversión de parámetros heterogéneos a HList , de la misma manera * convierte de parámetros homogéneos a Seq :

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

Ya no estamos hablando de la vida real de Scala, sino de una mejora hipotética. Esto me parece razonable, excepto que se supone que T es un tipo por la notación de parámetros de tipo. Podríamos, tal vez, simplemente extenderlo de la misma manera:

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

Para mí, parece que eso podría funcionar, aunque eso puede ser ingenuo por mi parte.

Consideremos una solución alternativa, una que depende de la unificación de listas de parámetros y tuplas. Digamos que Scala finalmente unificó la lista de parámetros y las tuplas, y que todas las tuplas eran subclases de una clase abstracta Tuple . Entonces podríamos escribir esto:

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

Ahí. Hacer una clase abstracta Tuple sería trivial, y la unificación de la tupla / lista de parámetros no es una idea descabellada.


Es posible escribir apply en un lenguaje de tipo estático, siempre que las funciones se escriban de una manera particular. En la mayoría de los idiomas, las funciones tienen parámetros individuales terminados ya sea por un rechazo (es decir, sin invocación variadic), o una aceptación escrita (es decir, posible invocación variadic, pero solo cuando todos los parámetros adicionales son del tipo T). Así es como puedes modelar esto en 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]]

Tenga en cuenta que esto no impone una buena formación (aunque creo que existen límites de tipo para eso), pero se entiende la idea. Entonces tienes la apply definida como esta:

apply[T, U]: (TypeList[T], (T => U)) => U

Sus funciones, entonces, se definen en términos de tipo de lista de cosas:

def f (x: Int, y: Int): Int = x + y

se convierte en:

def f (t: TypeList[Cons[Int, Cons[Int, Reject]]]): Int = t.head + t.tail.head

Y variad funciones como esta:

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

conviértete en esto:

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

El único problema con todo esto es que en Scala (y en la mayoría de los otros lenguajes estáticos), los tipos no son de primera clase para definir los isomorfismos entre cualquier estructura de estilo de contras y una tupla de longitud fija. Debido a que la mayoría de los lenguajes estáticos no representan funciones en términos de tipos recursivos, no tiene la flexibilidad de hacer cosas como estas de manera transparente. (Las macros cambiarían esto, por supuesto, además de fomentar una representación razonable de los tipos de funciones en primer lugar. Sin embargo, el uso de apply negativamente afecta el rendimiento por razones obvias).


Es trivial en 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, sin tipo:

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

Tal vez mezclé funcall y apply , entonces:

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


La razón por la que no puede hacerlo en la mayoría de los idiomas de tipo estático es que casi todos eligen tener un tipo de lista que se limita a las listas uniformes. Typed Racket es un ejemplo para un lenguaje que puede referirse a listas que no están tipificadas de manera uniforme (por ejemplo, tiene un Listof para listas uniformes, y List para una lista con una longitud estáticamente conocida que puede ser no uniforme), pero aún así asigna un tipo limitado (con listas uniformes) para la apply de Raqueta, ya que el tipo real es extremadamente difícil de codificar.



Una APLICACIÓN completa es difícil en un lenguaje estático.

En Lisp APPLY aplica una función a una lista de argumentos. Tanto la función como la lista de argumentos son argumentos para APLICAR.

  • APLICAR puede utilizar cualquier función. Eso significa que esto podría ser cualquier tipo de resultado y cualquier tipo de argumento.

  • APPLY toma argumentos arbitrarios en longitud arbitraria (en Common Lisp, la longitud está restringida por un valor constante específico de la implementación) con tipos arbitrarios y posiblemente diferentes.

  • APPLY devuelve cualquier tipo de valor que devuelve la función que obtuvo como argumento.

¿Cómo comprobaría un tipo que sin subvertir un sistema de tipo estático?

Ejemplos:

(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

Ejemplo de interacción:

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

Ahora un ejemplo con la función QUITAR. Vamos a eliminar el carácter a de una lista de cosas diferentes.

CL-USER 50 > (apply (READ) (READ)) remove (#/a (1 "a" #/a 12.3 :foo)) (1 "a" 12.3 :FOO)

Tenga en cuenta que también puede aplicar la aplicación en sí, ya que la aplicación es una función.

CL-USER 56 > (apply #''apply ''(+ (1 2 3))) 6

También hay una ligera complicación porque la función APLICAR toma un número arbitrario de argumentos, donde solo el último argumento debe ser una lista:

CL-USER 57 > (apply #''open "/tmp/foo1" :direction :input ''(:if-does-not-exist :create)) #<STREAM::LATIN-1-FILE-STREAM /tmp/foo1>

¿Cómo lidiar con eso?

  • relajar las reglas de verificación de tipo estático

  • restringir APLICAR

Uno o ambos de los anteriores deberán realizarse en un lenguaje de programación comprobado típico de tipo estático. Tampoco le dará una APLICACIÓN totalmente flexible y completamente estática.


Una lista en Haskell solo puede almacenar valores de un tipo, por lo que no podrías hacer cosas divertidas como (apply substring ["Foo",2,3]) . Haskell tampoco tiene funciones variables, por lo que (+) solo puede tomar dos argumentos.

Hay una función $ en Haskell:

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

Pero eso solo es realmente útil porque tiene una prioridad muy baja, o como algo que pasa alrededor de las HOF.

¿Imagino que podrías hacer algo como esto usando tipos de tuplas y 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

Supongo que es una especie de ''uncurryN'', ¿no es así?

Edición : esto no compila realmente; reemplazado por la respuesta de @ FUZxxl.


Vea su dinámica para haskell, en C, los punteros de función nula se pueden convertir a otros tipos, pero tendría que especificar el tipo para convertirlos. (Creo, no he hecho punteros de función en un tiempo)


prueba los pliegues. probablemente son similares a lo que quieres. Solo escribe un caso especial de ello.

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

incidentalmente, foldr1 es funcionalmente equivalente a foldr con el acumulador inicializado como el elemento de la lista.

Hay todo tipo de pliegues. técnicamente, todos hacen lo mismo, aunque de diferentes maneras, y podrían hacer sus argumentos en diferentes órdenes. foldr es solo uno de los más simples.