Cannot create apply function with static language?
Asked Answered
P

12

21

I have read that with a statically typed language like Scala or Haskell there is no way to create or provide a Lisp apply function:

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

or maybe

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

Is this a truth?

Portable answered 12/9, 2010 at 2:3 Comment(9)
C# is static and has a function called Invoke that is like apply.Mize
IIRC, C# does that using dynamics, which means that it'll generate glue code that checks all the I/O types before passing them.Fredric
one has to wonder why you want to.Tuscan
@sreservoir: In the general case, static typechecking is equivalent to solving the Halting Problem and thus undecidable. This means that there exist programs which are type-safe but not type-checkable. Put another way: any statically typed language will prevent you from writing certain perfectly type-safe programs. This is basically undisputed even by the most hardcore static typing fanbois. There is, however, a disagreement between proponents of static and dynamic typing about whether or not this class contains any useful programs. This basically asks whether apply is such a program.Riebling
C#'s Invoke function has been around since 1.0 so it uses reflection. Dynamically generating glue code is a 4.0 feature.Mize
In particular, apply and eval (especially a metarcicular eval) are some of the programs that are often claimed by proponents of dynamic typing to be impossible to implement in statically typed languages. At least in practically existing ones. For example, if you look at the source code for Haskell's dynApply in the Data.Dynamic module, which is probably the closest thing to Scheme's apply, you will find that it uses the unsafeCoerce function, which is basically the same as an unrestricted unsafe cast in C, and thus explicitly and deliberately circumvents the type system.Riebling
@Jörg W Mittag: Speaking as a "hardcore static typing fanboi" (C# and Java don't really count as statically typed languages BTW), I pretty much agree. There are workarounds, but there's basically no way to make a proper, first-class eval without circumventing the type system somehow. In fact, if you plug all the holes and take static typing to its logical conclusion, it's provably impossible to write an interpreter for a language in itself at all. Having used Scheme and Ruby before moving to Haskell, I do miss this sort of thing sometimes...Quaver
@Jörg: Any chance you want to convert your comments (both of them togeteher) into an actual answer, so I can upvote it?Eastern
Looking at most of the answers below, it looks like the issue that stopped people wasn't writing apply but having it to deal with types unknown at the compilation (or simply with tuples). Any language with meta programming facility allow to write apply, to a degree, at the condition that arguments types and arity are fixed at compile-time.Cointon
G
8

A full APPLY is difficult in a static language.

In Lisp APPLY applies a function to a list of arguments. Both the function and the list of arguments are arguments to APPLY.

  • APPLY can use any function. That means that this could be any result type and any argument types.

  • APPLY takes arbitrary arguments in arbitrary length (in Common Lisp the length is restricted by an implementation specific constant value) with arbitrary and possibly different types.

  • APPLY returns any type of value that is returned by the function it got as an argument.

How would one type check that without subverting a static type system?

Examples:

(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

Interaction example:

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

Now an example with the function REMOVE. We are going to remove the character a from a list of different things.

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

Note that you also can apply apply itself, since apply is a function.

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

There is also a slight complication because the function APPLY takes an arbitrary number of arguments, where only the last argument needs to be a list:

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

How to deal with that?

  • relax static type checking rules

  • restrict APPLY

One or both of above will have to be done in a typical statically type checked programming language. Neither will give you a fully statically checked and fully flexible APPLY.

Guardhouse answered 12/9, 2010 at 5:57 Comment(26)
BTW, this IS possible whithout restrictions, see my posts. The only thing is, that I didn't got the compiler to auto-infer types.Hambley
@FUZxxl: How so, your post is full of restrictions and the 'does not auto-infer types' relaxes static typing.Guardhouse
This answer confuses dealing with unknown values like the result of read with the problem of typing apply. A statically typed apply would obviously work only on lists of statically known length containing values of statically known types. The resulting apply will not be be the same as the Lisp version in the same way that a simple (lambda (f x) (f x)) is more restricted in a statically typed language.Fredric
@Eli Barzilay, READ is just an example. Take COMPILE, EVAL. In Lisp there are functions that can return any type of functions. APPLY is able to call any of them.Guardhouse
@Rainer Joswig: Yes, but see that (lambda (f x) (f x)) example -- it can call them all too, yet a function like that is perfectly typeable in static languages. In many of them, you won't be able to use it with a read function, since there isn't one. A notable exception is Typed Racket, which is a statically typed language that can deal fine with such functions and statically-unknown values.Fredric
@Eli Barzilay, a typical use case of APPLY is to use it with variable length argument lists and arguments of different types. If such a use case can not be replicated using APPLY in a statically typed language, then it is not the APPLY that Lisp provides. The OP asked about a Lisp apply function. If one restricts APPLY to what the statically typed language allows, then it covers only simple uses of APPLY, but is not a Lisp apply, which the OP was asking about.Guardhouse
@Eli Barzilay, similar if I can't call APPLY with any function, functions computed at runtime, it is also not Lisp APPLY - whatever 'obvious' it is to you.Guardhouse
Following this line, you're reducing the answer to a trivial "no", for reasons that are unrelated to apply, and with that you completely ignore an interesting problem that points at a real deficiency of popular type systems.Fredric
@Eli Barzilay, the reasons are not unrelated to APPLY. APPLY is used for certain types of use cases in Lisp. I describe some of the capabilities, to make sure that the OP understands that APPLY is not a case of REDUCE, but used for much more in Lisp. That these use cases of APPLY are hard to replicate in a statically typed language is not my problem.Guardhouse
Yes, I know that there was lots of confusion here about reduce -- I mentioned it in lots of comments. But in any case, it's obvious that the real question is not your problem, since instead of addressing it your answer goes back to the dynamic vs static debate.Fredric
@Eli Barzilay. but the OP was actually asking whether the Lisp APPLY function, which is tightly bound to the dynamically typed nature of Lisp, can be replicated in a statically typed language - just read his question. I explained why that is difficult -> because there are use cases of APPLY that are not easy to replicate with static typing. The REDUCE case only one. I showed for example that changing argument lists at runtime and passing those via APPLY to a function is another use case. Then I showed an example where a use case is to call different functions with argument lists via APPLY.Guardhouse
If I understand the problem correctly, is almost trivial with dependent types (a la Coq)Agrology
Rainer: the Lisp apply is "tightly bound to the dynamically typed nature of Lisp" in exactly the same way that any other HOF is. It's easy to give examples that use read and other statically-unknown values (incl. functions) for all of these functions -- for example, (mapcar (read) (read)) is exactly the same kind of demonstration. Yet, I don't see any Lispers walking around claiming that you can't write mapcar in a statically typed language.Fredric
So -- the original question, being specific about apply rather than other HOFs, is best understood as a question on it, rahter than on the merits of dynamic typing. It's true that for most HOFs, a statically typed implementation is easy and results in a polymorphic type. But apply defies that on two fronts: first, the type itself is much more complicated, and second, apply is doing something that cannot be done without support from the language: it reifies a first-class value (lists) as a second class one (argument lists). All of that is the interesting stuff that you just ignored.Fredric
@Eli Barzilay, no APPLY is not like any other HOF and has nothing to do with mapcar. It's specific purpose is to be able to call arbitrary functions with arbitrary argument lists that are computed at runtime. Thus its purpose is tightly linked with the use cases I have provided examples for.Guardhouse
@Eli Barzilay, for example in Lisp there are no restrictions created by the choice of the data structure used for the argument lists. There are no restrictions that the list can contain only one type, has to be of a fixed lengths, can contaim only certain types, etc. In Lisp every possible arglist can be constructed at runtime. If static typing imposes limitations on possible arglists that could be passed to some kind of apply functions, these are not present in Lisp.Guardhouse
Right. Same argument as mapcar ("It's specific purpose is to be able to call arbitrary functions with arbitrary argument lists that are computed at runtime"), still irrelevant.Fredric
@Eli Barzilay, no it isn't irrelevant. APPLY is the mechanism that one uses to implement MAPCAR in Lisp when flexible calling is needed. APPLY is exactly the tool for that. MAPCAR's purpose is to map a function over one or more lists and return a list of returned values.Guardhouse
Yet implementing a mapcar in a static language is not only possible, it's easy, and they all have one.Fredric
@Eli Barzilay, I can't implement MAPCAR with flexible calling conventions (mapping over arbitrary number of lists) without APPLY. That's one of the use cases why APPLY is there. That a feature exist does not say anything about its purpose and how its implemented. Eli I fear I don't see how your arguments apply to the topic at hand and much of that is from a perspective that leaves out any 'pragmatics'. I'll leave at at that. Have a nice day.Guardhouse
... And so you're back to saying that a Lisp mapcar is also "hard" to do in static languages.Fredric
@Eli Barzilay, you wanted to discuss mapcar, not me. I was talking about APPLY, whose purpose is to be able call functions with computed argument lists. Where I gave examples which are difficult to replicate in some statically typed programming languages.Guardhouse
No, you gave an explanation that revolves completely on dynamic vs static typing -- which is a different question. Specifically, the examples you gave would all work with the statically typed apply function in Typed Racket.Fredric
@Eli Barzilay: the original question for you to read, emphasis by me: 'have read that with a STATICALLY TYPED LANGUAGE like SCALA or HASKELL there is no way to create or provide a LISP APPLY function'. Eli show me the full APPLY of Lisp in Scala or Haskell, statically typed. Please don't tell me about mapcar or Typed Racket.Guardhouse
Typed Racket is a STATICALLY TYPED LANGUAGE. It provides a STATICALLY TYPED LISP APPLY function. (Capitalizations following your comment.)Fredric
@Eli Barzilay. From your answer: 'uniform' lists with 'limited types' or 'lists' with statically known length. Doesn't sound like Lisp's APPLY, which has neither of these restrictions. Using APPLY with uniform lists sounds mostly useless to me, especially since the main use case can be provided via REDUCE. Using APPLY with lists of a static length also sounds strange.Guardhouse
L
10

It is perfectly possible in a statically typed language. The whole java.lang.reflect thingy is about doing that. Of course, using reflection gives you as much type safety as you have with Lisp. On the other hand, while I do not know if there are statically typed languages supporting such feature, it seems to me it could be done.

Let me show how I figure Scala could be extended to support it. First, let's see a simpler example:

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

This is real Scala code, and it works, but it won't work for any function which receives arbitrary types. For one thing, the notation T* will return a Seq[T], which is a homegenously-typed sequence. However, there are heterogeneously-typed sequences, such as the HList.

So, first, let's try to use HList here:

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

That's still working Scala, but we put a big restriction on f by saying it must receive an HList, instead of an arbitrary number of parameters. Let's say we use @ to make the conversion from heterogeneous parameters to HList, the same way * converts from homogeneous parameters to Seq:

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

We aren't talking about real-life Scala anymore, but an hypothetical improvement to it. This looks reasonably to me, except that T is supposed to be one type by the type parameter notation. We could, perhaps, just extend it the same way:

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

To me, it looks like that could work, though that may be naivety on my part.

Let's consider an alternate solution, one depending on unification of parameter lists and tuples. Let's say Scala had finally unified parameter list and tuples, and that all tuples were subclass to an abstract class Tuple. Then we could write this:

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

There. Making an abstract class Tuple would be trivial, and the tuple/parameter list unification is not a far-fetched idea.

Lugubrious answered 12/9, 2010 at 16:6 Comment(4)
Using any form of reflection is cheating the solution, since it "gives you as much type safety as you have with Lisp". A proper solution would be a typed apply -- much like $ being close to a statically typed version of funcall.Fredric
@Eli Only the first paragraph talks about a solution with reflection. All the rest is plain static typing.Lugubrious
Daniel: Indeed, you're not talking about reflection; but you're imaginary "Let's say we use @ to make the conversion from heterogeneous parameters to HList" is exactly the problem here! You can't just switch from a type to a sequence of types without some serious support from the language: an hlist is still a value in the language, and without apply a sequence of function argument is not something in the language. It's a second class concept that you can't access directly. As for the question if it could work, of course it can -- I've pointed at Typed Racket... (It's just hard.)Fredric
(I should have added that: yes, it's possible, just not done for pretty much all statically typed languages (including Scala) -- with Typed Racket being an obvious exception.)Fredric
F
8

The reason you can't do that in most statically typed languages is that they almost all choose to have a list type that is restricted to uniform lists. Typed Racket is an example for a language that can talk about lists that are not uniformly typed (eg, it has a Listof for uniform lists, and List for a list with a statically known length that can be non-uniform) -- but still it assigns a limited type (with uniform lists) for Racket's apply, since the real type is extremely difficult to encode.

Fredric answered 12/9, 2010 at 3:0 Comment(7)
In Scala and Haskell, those are called Tuples. They encode their length and the type of each element.Seldon
In addition to Tuples, there are HLists (heterogenous lists). Like Tuples, they encode length and type of arguments. Unlike Tuples, they don't require a new type constructor for each arity, and support operations like typed concatenate. A good bit tougher to use than tuples, but they really show off what is possible with modern typeful programming. homepages.cwi.nl/~ralf/HListScuppernong
@MJP Tuples are not the same thing. An HList would be more like it -- an arbitrary-length data structure that is fully typed at each member. And there exist such for Scala and Haskell, by the way.Lugubrious
MJP: no, tuples are not really the same as what I'm talking about, and trying to reify them as argument lists in Haskell can be confusing since Haskell has only unary functions. I don't know anything specific about HLists, but it sounds much closer to what I'm talking about.Fredric
Eli Barzilay: HList is a library for working with lists containing values of arbitrary types. The "type" of an HList is also a list, holding the types for each item. Basically equivalent to right-nested 2-tuples with () as the empty list. Deep wizardry is used to create functions working on arbitrary HLists, but to do much of anything useful all the types must still be known at compile-time.Quaver
camccann: Yeah, that sounds right; the next step that is needed is to have subtyping... (But I won't be holding my breath...)Fredric
@Eli Barzilay: Yeah, not really going to get much in the way of subtyping relationships in Haskell. Maybe Scala? Also keep in mind that type metaprogramming in Haskell is Turing-complete with the right compiler flag, which HList requires anyway, so if you do know the types at compile-time you can do pretty much anything you like.Quaver
G
8

A full APPLY is difficult in a static language.

In Lisp APPLY applies a function to a list of arguments. Both the function and the list of arguments are arguments to APPLY.

  • APPLY can use any function. That means that this could be any result type and any argument types.

  • APPLY takes arbitrary arguments in arbitrary length (in Common Lisp the length is restricted by an implementation specific constant value) with arbitrary and possibly different types.

  • APPLY returns any type of value that is returned by the function it got as an argument.

How would one type check that without subverting a static type system?

Examples:

(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

Interaction example:

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

Now an example with the function REMOVE. We are going to remove the character a from a list of different things.

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

Note that you also can apply apply itself, since apply is a function.

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

There is also a slight complication because the function APPLY takes an arbitrary number of arguments, where only the last argument needs to be a list:

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

How to deal with that?

  • relax static type checking rules

  • restrict APPLY

One or both of above will have to be done in a typical statically type checked programming language. Neither will give you a fully statically checked and fully flexible APPLY.

Guardhouse answered 12/9, 2010 at 5:57 Comment(26)
BTW, this IS possible whithout restrictions, see my posts. The only thing is, that I didn't got the compiler to auto-infer types.Hambley
@FUZxxl: How so, your post is full of restrictions and the 'does not auto-infer types' relaxes static typing.Guardhouse
This answer confuses dealing with unknown values like the result of read with the problem of typing apply. A statically typed apply would obviously work only on lists of statically known length containing values of statically known types. The resulting apply will not be be the same as the Lisp version in the same way that a simple (lambda (f x) (f x)) is more restricted in a statically typed language.Fredric
@Eli Barzilay, READ is just an example. Take COMPILE, EVAL. In Lisp there are functions that can return any type of functions. APPLY is able to call any of them.Guardhouse
@Rainer Joswig: Yes, but see that (lambda (f x) (f x)) example -- it can call them all too, yet a function like that is perfectly typeable in static languages. In many of them, you won't be able to use it with a read function, since there isn't one. A notable exception is Typed Racket, which is a statically typed language that can deal fine with such functions and statically-unknown values.Fredric
@Eli Barzilay, a typical use case of APPLY is to use it with variable length argument lists and arguments of different types. If such a use case can not be replicated using APPLY in a statically typed language, then it is not the APPLY that Lisp provides. The OP asked about a Lisp apply function. If one restricts APPLY to what the statically typed language allows, then it covers only simple uses of APPLY, but is not a Lisp apply, which the OP was asking about.Guardhouse
@Eli Barzilay, similar if I can't call APPLY with any function, functions computed at runtime, it is also not Lisp APPLY - whatever 'obvious' it is to you.Guardhouse
Following this line, you're reducing the answer to a trivial "no", for reasons that are unrelated to apply, and with that you completely ignore an interesting problem that points at a real deficiency of popular type systems.Fredric
@Eli Barzilay, the reasons are not unrelated to APPLY. APPLY is used for certain types of use cases in Lisp. I describe some of the capabilities, to make sure that the OP understands that APPLY is not a case of REDUCE, but used for much more in Lisp. That these use cases of APPLY are hard to replicate in a statically typed language is not my problem.Guardhouse
Yes, I know that there was lots of confusion here about reduce -- I mentioned it in lots of comments. But in any case, it's obvious that the real question is not your problem, since instead of addressing it your answer goes back to the dynamic vs static debate.Fredric
@Eli Barzilay. but the OP was actually asking whether the Lisp APPLY function, which is tightly bound to the dynamically typed nature of Lisp, can be replicated in a statically typed language - just read his question. I explained why that is difficult -> because there are use cases of APPLY that are not easy to replicate with static typing. The REDUCE case only one. I showed for example that changing argument lists at runtime and passing those via APPLY to a function is another use case. Then I showed an example where a use case is to call different functions with argument lists via APPLY.Guardhouse
If I understand the problem correctly, is almost trivial with dependent types (a la Coq)Agrology
Rainer: the Lisp apply is "tightly bound to the dynamically typed nature of Lisp" in exactly the same way that any other HOF is. It's easy to give examples that use read and other statically-unknown values (incl. functions) for all of these functions -- for example, (mapcar (read) (read)) is exactly the same kind of demonstration. Yet, I don't see any Lispers walking around claiming that you can't write mapcar in a statically typed language.Fredric
So -- the original question, being specific about apply rather than other HOFs, is best understood as a question on it, rahter than on the merits of dynamic typing. It's true that for most HOFs, a statically typed implementation is easy and results in a polymorphic type. But apply defies that on two fronts: first, the type itself is much more complicated, and second, apply is doing something that cannot be done without support from the language: it reifies a first-class value (lists) as a second class one (argument lists). All of that is the interesting stuff that you just ignored.Fredric
@Eli Barzilay, no APPLY is not like any other HOF and has nothing to do with mapcar. It's specific purpose is to be able to call arbitrary functions with arbitrary argument lists that are computed at runtime. Thus its purpose is tightly linked with the use cases I have provided examples for.Guardhouse
@Eli Barzilay, for example in Lisp there are no restrictions created by the choice of the data structure used for the argument lists. There are no restrictions that the list can contain only one type, has to be of a fixed lengths, can contaim only certain types, etc. In Lisp every possible arglist can be constructed at runtime. If static typing imposes limitations on possible arglists that could be passed to some kind of apply functions, these are not present in Lisp.Guardhouse
Right. Same argument as mapcar ("It's specific purpose is to be able to call arbitrary functions with arbitrary argument lists that are computed at runtime"), still irrelevant.Fredric
@Eli Barzilay, no it isn't irrelevant. APPLY is the mechanism that one uses to implement MAPCAR in Lisp when flexible calling is needed. APPLY is exactly the tool for that. MAPCAR's purpose is to map a function over one or more lists and return a list of returned values.Guardhouse
Yet implementing a mapcar in a static language is not only possible, it's easy, and they all have one.Fredric
@Eli Barzilay, I can't implement MAPCAR with flexible calling conventions (mapping over arbitrary number of lists) without APPLY. That's one of the use cases why APPLY is there. That a feature exist does not say anything about its purpose and how its implemented. Eli I fear I don't see how your arguments apply to the topic at hand and much of that is from a perspective that leaves out any 'pragmatics'. I'll leave at at that. Have a nice day.Guardhouse
... And so you're back to saying that a Lisp mapcar is also "hard" to do in static languages.Fredric
@Eli Barzilay, you wanted to discuss mapcar, not me. I was talking about APPLY, whose purpose is to be able call functions with computed argument lists. Where I gave examples which are difficult to replicate in some statically typed programming languages.Guardhouse
No, you gave an explanation that revolves completely on dynamic vs static typing -- which is a different question. Specifically, the examples you gave would all work with the statically typed apply function in Typed Racket.Fredric
@Eli Barzilay: the original question for you to read, emphasis by me: 'have read that with a STATICALLY TYPED LANGUAGE like SCALA or HASKELL there is no way to create or provide a LISP APPLY function'. Eli show me the full APPLY of Lisp in Scala or Haskell, statically typed. Please don't tell me about mapcar or Typed Racket.Guardhouse
Typed Racket is a STATICALLY TYPED LANGUAGE. It provides a STATICALLY TYPED LISP APPLY function. (Capitalizations following your comment.)Fredric
@Eli Barzilay. From your answer: 'uniform' lists with 'limited types' or 'lists' with statically known length. Doesn't sound like Lisp's APPLY, which has neither of these restrictions. Using APPLY with uniform lists sounds mostly useless to me, especially since the main use case can be provided via REDUCE. Using APPLY with lists of a static length also sounds strange.Guardhouse
L
4

It's trivial in 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, typeless:

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

Perhaps I mixed up funcall and apply, so:

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
Lithe answered 12/9, 2010 at 2:47 Comment(12)
No, these reduce, fold, and friends are not apply.Fredric
Please see my further example. Thank youPortable
I guess I need to see a definition of what you want rather than just examples. But I think it's a near certainty that whatever it is you want to do, it can be done in a reasonable fashion in Scala.Lithe
The definition of apply in CL is simply "This applies function to a list of arguments.", and in R5RS it's defined as "Calls proc with the elements of the list [...] as the actual arguments. (In any case, I highly doubt your "near certainty".)Fredric
@Randall: apply in Lisp is equivalent to java.lang.reflect.Method#invoke in Java. No relation to folds whatsoever.Leila
I see. You want all your type errors at run time. "Have it your way"™Lithe
@Missing Faktor: That example matches the original example in the question.Lithe
@Randall Schulz: That's not really fair--it's not about "type errors at runtime", it's about first-class metaprogramming. This is absolutely reasonable and useful and awesome and really tough to get right in a statically typed language. In an ideal world, I'd be able to check at compile-time not only that my program is well-typed, but that my meta-programming will itself only generate well-typed programs at run time. This is a hard problem, but not impossible!Quaver
@camccann: But it is. If you just want to be able to apply any and every function to any and every argument list, you have no typing at all. If you want typing, then you have to say what types you're dealing with and this sort of generic apply is not possible. I would say it's not desirable. But that's not to say metaprogramming is infeasible or undesirable, just that it necessarily takes a different form in a statically typed language.Lithe
@Randall Sure it could be possible, if the language provided compile-time support for type lists. Then we could write something like this: def apply[@T, R](f: (@T => R), args: @T*): R = f(args: _*), where @T stands for a list of types, and args was something like an HList.Lugubrious
@Randall Schulz: No, the idea is to be able to apply any function to any argument list of the correct type, even when neither the function, the arguments, nor their types are known at compile time, e.g. because the type depends on input received at run time. In the general case this is called dependent typing and is not only possible but is implemented in several languages. The catch is that it's very difficult to use, because type signatures end up being, essentially, proofs of correctness.Quaver
"type signatures end up being, essentially, proofs of correctness" you put it like is a bad thing :)Agrology
H
2

In Haskell, there is no datatype for multi-types lists, although I believe, that you can hack something like this together whith the mysterious Typeable typeclass. As I see, you're looking for a function, which takes a function, a which contains exactly the same amount of values as needed by the function and returns the result.

For me, this looks very familiar to haskells uncurryfunction, just that it takes a tuple instead of a list. The difference is, that a tuple has always the same count of elements (so (1,2) and (1,2,3) are of different types (!)) and there contents can be arbitrary typed.

The uncurry function has this definition:

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

What you need is some kind of uncurry which is overloaded in a way to provide an arbitrary number of params. I think of something like this:

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

But this only works, if ALL types involved are known to the compiler. Sadly, adding a fundep causes the compiler to refuse compilation. As I'm not a haskell guru, maybe domeone else knows, howto fix this. Sadly, I don't know how to archieve this easier.

Résumee: apply is not very easy in Haskell, although possible. I guess, you'll never need it.

Edit I have a better idea now, give me ten minutes and I present you something whithout these problems.

Hambley answered 12/9, 2010 at 3:34 Comment(4)
FUZxxl: for this to work, you'll need an infinite definition.Fredric
Yes. I saw some definition whithout infiniteness, but I forgot where. Let me look for it.Hambley
And I also know, it's a duplicate.Hambley
I tried for half an hour to make this user-friendly. The idea is to write a generalized flatTuple function of the type flatTuple :: (a, b...) -> (a,(b,...)) and apply the elements to the function recursivly. But by some reason, the type system is not happy with my idea sniffHambley
B
2

It is possible to write apply in a statically-typed language, as long as functions are typed a particular way. In most languages, functions have individual parameters terminated either by a rejection (i.e. no variadic invocation), or a typed accept (i.e. variadic invocation possible, but only when all further parameters are of type T). Here's how you might model this in 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]]

Note that this doesn't enforce well-formedness (though type bounds do exist for that, I believe), but you get the idea. Then you have apply defined like this:

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

Your functions, then, are defined in terms of type list things:

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

becomes:

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

And variadic functions like this:

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

become this:

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

The only problem with all of this is that in Scala (and in most other static languages), types aren't first-class enough to define the isomorphisms between any cons-style structure and a fixed-length tuple. Because most static languages don't represent functions in terms of recursive types, you don't have the flexibility to do things like this transparently. (Macros would change this, of course, as well as encouraging a reasonable representation of function types in the first place. However, using apply negatively impacts performance for obvious reasons.)

Brae answered 13/9, 2010 at 15:7 Comment(0)
T
1

try folds. they're probably similar to what you want. just write a special case of it.

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

incidentally, foldr1 is functionally equivalent to foldr with the accumulator initialized as the element of the list.

there are all sorts of folds. they all technically do the same thing, though in different ways, and might do their arguments in different orders. foldr is just one of the simpler ones.

Tuscan answered 12/9, 2010 at 2:5 Comment(8)
BTW, foldl is prefereable in most cases.Hambley
The apply function only calls the function once, with the supplied list as its arguments. The OP's example doesn't fold the list 1,2,3 with the add function, it calls the add function once with the list 1,2,3 and the add function does the looping itself.Mize
Folds, of any sort, are unrelated to apply.Fredric
@Eli Barzilay In general, folds are unrelated to apply, but for the specific example quoted in the question, they work as a substitute; so this answer would be a correct way of implementing the specific example in the question, but not as a general substitute for apply.Amin
and, of course, I just realized I completely missed the point of apply.Tuscan
@Brian Campbell: you're obviously right; but if all we care about is this specific example, then there's a whole bunch of ways it can be implemented without apply. For example -- "6" works fine too, and it's a much shorter program, even.Fredric
@Eli Barzilay Oh, sure. I'm just pointing out that on StackOverflow, sometimes a precise the answer to the question is the one the asker is looking for, but sometimes you need to guess at their intent and answer a somewhat different question. In this case, they are asking a somewhat general question, but giving a fairly specific example, so it may be useful to provide an answer that covers that use case without answering the general question. You're absolutely right that fold does not do the same thing as apply, other than for this one narrow use case.Amin
@Brian Campbell: Yes, I think that the new example that he added would clarify that.Fredric
S
1

On this page, I read that "Apply is just like funcall, except that its final argument should be a list; the elements of that list are treated as if they were additional arguments to a funcall."

In Scala, functions can have varargs (variadic arguments), like the newer versions of Java. You can convert a list (or any Iterable object) into more vararg parameters using the notation :_* Example:

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

In fact, even Java can do this. Java varargs can be passed either as a sequence of arguments or as an array. All you'd have to do is convert your Java List to an array to do the same thing.

Seldon answered 12/9, 2010 at 2:47 Comment(7)
The main point of apply is that the function that is being applied doesn't need to change.Fredric
What? Where did I change a function?Seldon
You've defined it with varargs:. OTOH, apply can be used with anything (even +, as the question shows).Fredric
On the contrary, Lisp's + is defined with varargs. In Lisp, you can say (+ 1 2 3 4).Seldon
MJP: that's right, but that's just one example. apply can work with any lisp function, including ones that are not variable arity. (I just edited it and added such an example.)Fredric
So? If you have a function that only expects 2 arguments, and you apply it with a list of 20 elements, you'd get an error at run time. That would be a bug, not a feature. The closest type-safe equivalent would be converting Tuples into multiple parameters. Scala's functions do have a tupled method which converts the whole parameter list into one Tuple.Seldon
MJP: The runtime error that you're talking about isn't too related here. The only minor connection is that statically typed languages almost always leave (car nil) as a runtime error, since they don't encode the length of the list in the type. So we can learn from this that a statically-type-safe implementation of apply requires that your type system is one that can talk about lists of a specific length. (But that still leaves a lot to do.)Fredric
V
1

The benefit of a static language is that it would prevent you to apply a function to the arguments of incorrect types, so I think it's natural that it would be harder to do.

Given a list of arguments and a function, in Scala, a tuple would best capture the data since it can store values of different types. With that in mind tupled has some resemblance to 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

For function of one argument, there is actually apply:

scala> val g = (i:Int) => i + 1
g: (Int) => Int = <function1>

scala> g.apply(2)
res11: Int = 3

I think if you think as apply as the mechanism to apply a first class function to its arguments, then the concept is there in Scala. But I suspect that apply in lisp is more powerful.

Vanitavanity answered 12/9, 2010 at 4:21 Comment(0)
D
0

For Haskell, to do it dynamically, see Data.Dynamic, and dynApp in particular: http://www.haskell.org/ghc/docs/6.12.1/html/libraries/base/Data-Dynamic.html

Deed answered 15/9, 2010 at 14:43 Comment(0)
E
0

See his dynamic thing for haskell, in C, void function pointers can be casted to other types, but you'd have to specify the type to cast it to. (I think, haven't done function pointers in a while)

Enroll answered 16/9, 2010 at 18:42 Comment(0)
C
-1

A list in Haskell can only store values of one type, so you couldn't do funny stuff like (apply substring ["Foo",2,3]). Neither does Haskell have variadic functions, so (+) can only ever take two arguments.

There is a $ function in Haskell:

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

But that's only really useful because it has very low precedence, or as passing around HOFs.

I imagine you might be able to do something like this using tuple types and fundeps though?

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

I guess that's a sort of 'uncurryN', isn't it?

Edit: this doesn't actually compile; superseded by @FUZxxl's answer.

Camden answered 12/9, 2010 at 2:33 Comment(4)
Nevermind; that doesn't work, I guess because they're overlapping. So I guess you'd need to do apply1 f t = f t, apply2 f (t1,t2) = f t1 t2, and so on...Camden
Variadic functions: Yes, we can! #3467779Hambley
Haskell's $ is not a form of an apply function -- it is more like funcall, except limited to one argument which makes it useless in Lisp. (But the one-argument limitation is obviously not a problem in Haskell.)Fredric
BTW, your won't compile, I tested. See my answerHambley

© 2022 - 2024 — McMap. All rights reserved.