Cannot create apply function with static language?

前端 未结 12 883
梦谈多话
梦谈多话 2021-02-04 02:09

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 #\'+ (lis         


        
相关标签:
12条回答
  • 2021-02-04 02:36

    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.

    0 讨论(0)
  • 2021-02-04 02:37

    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.

    0 讨论(0)
  • 2021-02-04 02:38

    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.

    0 讨论(0)
  • 2021-02-04 02:38

    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.

    0 讨论(0)
  • 2021-02-04 02:39

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

    0 讨论(0)
  • 2021-02-04 02:47

    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)

    0 讨论(0)
提交回复
热议问题