2

Is there a standard Haskell function (or pattern) to extract the contents of a list and feed them as though they are the ordered positional arguments to a function?

For example, consider the function (,) which, when given two positional arguments, will make a two-tuple from them:

(,) 3 4 --> (3,4)

Suppose instead I have these arguments given to me by some external function call that I cannot change, represented as a list [3, 4].

Is there a "contents of" operation, such that this would work:

(,) $ contents_of [3, 4]

so that the action of contents_of behaves just as though the items had been placed in source code with spaces between them as function application?

For example, (,) $ contents_of [1] should be the curried function ((,) 1) which then takes one more argument to complete creating the tuple.

One thought I had was to try to fold the function over the list, with the fold function expressing currying:

foldr (\x y -> y x) (,) [3, 4]

but looking at the type signature of foldr:

foldr :: (a -> b -> b) -> b -> [a] -> b

makes this seem difficult. b here would need to be the function type itself, but then by the time it has been applied to the arguments it won't be a function with the same type signature as b any longer, leading to type issues in the fold.

This is similar in spirit to the Python *args construct.

I'm not concerned with the strictness properties this might imply -- just whether something like this is possible in standard Haskell.

ely
  • 74,674
  • 34
  • 147
  • 228
  • `foldr` won't work because it can't stop; the signature of the function would change into compile-time error I think. – Bartek Banachewicz Nov 17 '14 at 13:36
  • Yes, I mentioned this. The other folds all have similar problems. They expect the type of the accumulator's final value to be the same as its starting value, but the intermediate curried functions will have different types. So the question is how else to do it. – ely Nov 17 '14 at 13:37
  • Wouldn't you also encounter a type problem when the length of the list excedes the number of arguments to a function? I don't think this can be done with regular lists, but it might be possible with a list with the notion of maximal length (and some optional added polymorphism). – Josh Kirklin Nov 17 '14 at 13:40
  • Yeah, there are a lot of failure modes, but for the moment I am fine if it produces errors when called incorrectly. If a person calls with too many arguments, a type error is fine. – ely Nov 17 '14 at 13:43
  • The problem is that such errors are found at compile time. Standard lists don't have a type level length, so an attempt to do this using only them will I believe always result in compile time failure. Like I said, I think you could do something with a different list type with added type structure. – Josh Kirklin Nov 17 '14 at 13:45

5 Answers5

4

It is possible to represent N-ary functions like so:

data FunN r a = FunN Int (a -> FunN r a) | FNil r

Then convert plain functions into FunN:

f2FunN :: (FunN (a->b) a) -> FunN b a
f2FunN (FNil g)   = FunN 1 (FNil . g)
f2FunN (FunN n g) = FunN (n+1) (f2FunN . g)

Then apply a list of arguments:

a :: FunN b a -> [a] -> b
a (FNil r)   []    = r
a (FunN _ f) (x:t) = a (f x) t
a _          _     = error "wrong arity"

For example:

Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1,2]
3
Prelude> a (f2FunN $ FNil (+)) [1] 2
3
Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1,2,3]
*** Exception: wrong arity
Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1]
*** Exception: wrong arity

But of course you need to know the arity of the function at compile time - so that you know how many times you can wrap the function with f2FunN.

Sassa NF
  • 5,306
  • 15
  • 22
  • This is a nice idea to think more about. Thanks! – ely Nov 17 '14 at 16:01
  • I'm wondering if there's a way to use something like `Control.Applicative`, and to build a list that is like `f2Fun_list = [f2FunN | x <- arg_list]` and then repeatedly apply the values from this with a fold? – ely Nov 17 '14 at 16:23
  • @prpl.mnky.dshwshr I'm sorry but this is too a *simulated* arity (you can't use directly your function "you need to know the arity" then, use `i2, i3, ...` instead!). In Haskell overload functions for each arity is usual (`lift2`, `lift3`, ..., `zip`, `zip3`, ...) but, on the other hand, it's avoided using `Applicative`. I suggest to you look for "The Haskell way" :D – josejuan Nov 17 '14 at 16:46
  • @josejuan I have heard your point from the other answer and reflected on it and don't agree with it. Yes, this is also simulated arity -- I didn't say otherwise and said this was a clever answer *to think further about* (not that it directly solves the question). I appreciate your perspective and remarks. You do not need to keep reiterating them -- I am not convinced by them and if that is just my own error then it will take me working through code to discover it, not lots of reiteration from you in comments. – ely Nov 17 '14 at 16:54
  • Wow. People are providing answers and being helpful. There is no need to attempt to put them down in that way. – Josh Kirklin Nov 17 '14 at 17:01
  • @JoshKirklin I do not see how I am putting anyone down. I'm getting frustrated because I have considered that line of thinking and I don't find it helpful. It's not "wrong", it's not bad, I appreciate that line of thinking, etc. etc., but I am not forced to find it convincing. Yet here on another answer there is a totally unrelated comment about simulated arity that is not helpful and does come off sounding like a criticism of this approach in contrast to the first answer. You can view my comments as if I am being antagonistic if you want, but I do not think I am expressing antagonism at all. – ely Nov 17 '14 at 17:23
  • @josejuan exactly. I wish the bickering was done and over with. – Sassa NF Nov 17 '14 at 17:29
3

As I explained in the comments above, I don't think this is possible with the standard list. So let's introduce a new list where each element in the list can be a different type, and this is encoded in the type of the list:

{-# LANGUAGE GADTs, MultiParamTypeClasses, FlexibleInstances #-}

data Nil
data TList a b where
    TEmpty :: TList Nil Nil
    (:.) :: c -> TList d e -> TList c (TList d e)
infixr 4 :.

TEmpty here is analogous to [], and :. to :. So we could have a list of an Int, and a couple of Bools: 31 :. True :. False :. TEmpty. The type of this list is TList Int (TList Bool (TList Bool (TList Nil Nil))).

We can now introduce a typeclass which provides a function which can be used to apply any arbitrary function to the list in the way you propose, given that the types match.

class TApplies f h t r where
    tApply :: f -> TList h t -> r

instance TApplies a Nil Nil a where
    tApply a _ = a

instance TApplies f h t r => TApplies (a -> f) a (TList h t) r where
    tApply f (e :. l) = tApply (f e) l

We can now use tApply to do something like what you want. Note that something like the following will not compile:

tApply (+) $ 1 :. (2 :: Int) :. TEmpty

We have to explicity type annotate everything:

tApply ((+) :: Int -> Int -> Int) $ (1 :: Int) :. (2 :: Int) :. TEmpty :: Int

This gives 3 as we might expect. I'm not sure how to get around this necessity; I expect some clever use of FunctionalDependencies would do the trick.

Josh Kirklin
  • 929
  • 5
  • 14
2

I use usually match patterns like

let (a:b:c:_) = args
in  func a b c

or inlined

(\(a:b:c:_) -> func a b c) args

if you really want do that you can create one operator to "inyect" element lists to any function

showAdd2 <<< args

but I think is not very usable...

{-# LANGUAGE FlexibleInstances, UndecidableInstances, ConstraintKinds, InstanceSigs, MultiParamTypeClasses #-}
class App a b c where
  (<<<) :: a -> [b] -> c

instance App (b -> b -> c) b c where -- 2-arity
  (<<<) f (a:b:_) = f a b

instance App (b -> b -> b -> c) b c where -- 3-arity
  (<<<) f (a:b:c:_) = f a b c

instance App (b -> b -> b -> b -> c) b c where -- 4-arity
  (<<<) f (a:b:c:d:_) = f a b c d

showAdd2 :: Int -> Int -> String
showAdd2 a b = show (a + b)

showAdd3 :: Int -> Int -> Int -> String
showAdd3 a b c = show (a + b + c)

main = do

    let args = [5..8] :: [Int]

        r2 = showAdd2 <<< args
        r3 = showAdd3 <<< args

    putStrLn r2
    putStrLn r3

a more usable version could be

i2 :: (b -> b -> c) -> [b] -> c
i2 f (a:b:_) = f a b

but you must to select the proper "inyector" for each case

showAdd2 `i2` [4..5]

(In Haskell the use of algebraic data types, lists and classes replace properly the need of polyvariadic functions. As @user5402 say, you should provide some explicit problem)

josejuan
  • 9,338
  • 24
  • 31
  • The problem is that I need this for N-arity, for arbitrary N. I think it's related to the Haskell polyvariadic function question. – ely Nov 17 '14 at 13:52
  • @prpl.mnky.dshwshr Haskell have not polyvariadic functions but can simulate it (as in my example or as http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function ). – josejuan Nov 17 '14 at 13:56
  • @prpl.mnky.dshwshr You should give us an example of why you want to define an N-arity function. – ErikR Nov 17 '14 at 14:03
  • [Others claim you can implement polyvariadic functions in Haskell](http://okmij.org/ftp/Haskell/polyvariadic.html#polyvar-fn). – ely Nov 17 '14 at 14:11
  • @user5402 I don't get into these kinds of debates. If you don't think that what I want is an OK thing for me to want, that's OK -- you can ignore the question. But I'm not going to duplicate everything about my current work problem to try to convince you that what I want is indeed the right thing to want for my problem at hand. I'm just asking -- if it's an interesting question and you want to work on it, feel free. If you don't like it because you think the premise is faulty, then just ignore it. I *hate* the SO tendency to criticize *why* someone asks a question instead of just working on it – ely Nov 17 '14 at 14:13
  • I think people usually ask 'why' only to get a better grasp of the needs of the asker, so that they can be of more help. I don't think it's a challenge. – Josh Kirklin Nov 17 '14 at 14:22
  • I understand, I didn't mean to say that @user5402 was being antagonistic. But what always happens is that a lot of people start asking "why" -- then the OP takes a bunch of time to explain why -- then (usually glossing over all of the nuances the OP explains) people start saying "oh, well if *that's* what you're trying to do, then you are going about it all wrong, and you should just do this other thing." That's the part I dislike. That's why I try to write a very narrow question that just asks one thing (how to unpack args from a list, in this case) and do not try to bring in anything else. – ely Nov 17 '14 at 14:26
  • @prpl.mnky.dshwshr I often ask people what they are trying to do in order to avoid the XY problem. It's quite frequent that people want to do something in Haskell that is idiomatic in another language (i.e. Python and `*args`) but it's not the easy way to do it in Haskell. Often there is a way to do it in Haskell, but it is inefficient or very difficult. If you're confident that you're trying to do things The Right Way(tm), then by all means continue down that path, otherwise share your goal with a [MVCE](http://stackoverflow.com/help/mcve). People are trying to help you, not criticize you. – bheklilr Nov 17 '14 at 14:28
  • I wish I could add a banner to every question that says, "Please assume I have considered how to solve my bigger problem, and I have determined that an answer to this particular narrower-scope question is needed to proceed." When people demand examples, it's like they are saying, "it's not possible for an answer to your question to actually help you in whatever you're trying to do" or else "you are misguided in wanting to do anything like this." That might be true for inexperienced programmers, but it should not be a default or common assumption! – ely Nov 17 '14 at 14:29
  • @bheklilr I think the example of `(,) 3 4` being replaced with some function `(,) $ contents_of [3, 4]` is a very clear minimal example -- my actual problem doesn't involve constructing tuples, but the principle is clear in a minimal way. – ely Nov 17 '14 at 14:39
  • @prpl.mnky.dshwshr I understand your frustration, but I am confused as to why you're being so defensive. You seem to have some examples of how to do what you want (even if it's not very clean in Haskell), and only after that did people ask what you're actually trying to do so they can propose alternate solutions. If you don't want the help, then just don't reply. – bheklilr Nov 17 '14 at 14:46
  • "Others claim..." no @prpl.mnky.dshwshr, I repeat "Haskell have not polyvariadic functions but can simulate it" they do just that. If you are looking for the best way to perform some thing, show explicitly your problem (but your question is about my `<<<` operator). And I agree with @bheklilr. – josejuan Nov 17 '14 at 14:49
  • @josejuan Did you follow my link from earlier -- it's not just a claim, but also source code implementing polyvariadic functions. It relies on the MultiParameterTypeClasses extension. – ely Nov 17 '14 at 14:52
  • @bheklilr The example of `(,) 3 4` to `(,) $ contents_of [3, 4]` is clear enough. That's the goal. It's frustrating that anyone needs to ask further, because that is a concise, minimal example of what I'm trying to do. *Why* I'm trying to do it isn't important to this question. If the answer is "this is provably impossible in Haskell and here is the proof" that's OK. If the answer is "this is not recommended but here you go" that's also OK. But an answer like "stop wanting to do that, go back 3 levels in your approach to the meta-problem you have, and just want to do something else" is not OK. – ely Nov 17 '14 at 14:56
  • @josejuan [Here is that polyvariadic source code](http://okmij.org/ftp/Haskell/vararg-fn.lhs). – ely Nov 17 '14 at 15:04
  • @prpl.mnky.dshwshr No one suggested that. In fact, user5402 only said "You should give us an example of why you want to define an N-arity function.", nothing more. I'm sorry if you've experienced this sort of push back on SO before, but I don't see that happening in this instance. Regardless, it should not necessitate the hostility you're retaliating with. I would encourage you to be more professional on SO, you'll get more people willing to help you. When others see that you're writing angry essays over common questions in the comments, they won't want to post more answers. – bheklilr Nov 17 '14 at 15:07
  • @bheklilr What relevance is there for giving an example? Suppose I say that pattern matching, for any fixed N, won't completely solve my problem. Why isn't it enough to just say that? Why, if not to try to suggest some other approach entirely, would it matter what the specific example is? The problem statement remains the same in all cases: accept a list (of whatever length) whose contents are to be fed as arguments to a function. *That* is the question I'm asking. I'm not asking about some different example I might have. – ely Nov 17 '14 at 15:16
2

Why Lists themselves don't work

In the narrowest sense of the question,

Is there a standard Haskell function (or pattern) to extract the contents of a list and feed them as though they are the ordered positional arguments to a function?

this has a very simple answer "no". The reason why such a function does not exist is because the type signature cannot easily make sense. The type signature you're asking for is:

applyToList :: (a -> c) -> [a] -> d

where the c either has the form a -> c' (in which case we recurse the definition) or else has the type d itself. None of the Prelude functions have wacky type signatures.

If you actively ignore this and try anyway, you will get the following error:

Prelude> let applyToList f list = case list of [] -> f; x : xs -> applyToList (f x) xs

<interactive>:9:71:
    Occurs check: cannot construct the infinite type: t1 ~ t -> t1
    Relevant bindings include
    xs :: [t] (bound at <interactive>:9:52)
    x :: t (bound at <interactive>:9:48)
    list :: [t] (bound at <interactive>:9:19)
    f :: t -> t1 (bound at <interactive>:9:17)
    applyToList :: (t -> t1) -> [t] -> t -> t1
        (bound at <interactive>:9:5)
    In the first argument of ‘applyToList’, namely ‘(f x)’
    In the expression: applyToList (f x) xs

The problem here is that when the typechecker tries to unify c with a -> c it constructs an infinite type; it has a cycle-detection algorithm which stops it, so it prematurely errors out.

There is a more fundamental problem here, which is the question of what applyTo (+) [3] should yield. Here (+) has type n -> n -> n for some n. The morally right answer is (3+) :: n -> n; but if you really want to consume all of the arguments of your list you probably want it to return undefined :: n instead. The reason that you cannot use the morally right answer is because you reject the definition applyTo f = f . head (which is typeable and does the above). The problem in the abstract is that the length of [3] is not known until run-time. You could insert an arbitrary expression in there. You could try to run this on an array which has length 1 if Goldbach's conjecture is true, or else length 2 if it is not; is the type system supposed to solve Goldbach's conjecture for you?

How to make them work

That last point actually contains the solution. We need to annotate the functions with their arities:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, FunctionalDependencies, UndecidableInstances #-}
-- no constructors; these exist purely in the type domain:
data Z
data S n

class Reducible f a x | f -> a x where applyAll :: f -> [a] -> x

newtype Wrap x n = Wrap x -- n is a so-called "phantom type" here

rewrap :: Wrap x n -> Wrap x (S n)
rewrap (Wrap x) = Wrap x

wrap0 :: x -> Wrap x Z
wrap0 = Wrap

wrap1 = rewrap . wrap0
wrap2 = rewrap . wrap1
wrap3 = rewrap . wrap2
wrap4 = rewrap . wrap3

apply :: Wrap (a -> x) (S n) -> a -> Wrap x n
apply (Wrap f) a = Wrap (f a)

instance Reducible (Wrap x Z) a x where
    applyAll (Wrap x) [] = x
    applyAll (Wrap x) _ = error "function called on too many arguments"

instance (Reducible (Wrap y n) a x) => Reducible (Wrap (a -> y) (S n)) a x where
    applyAll (Wrap f) [] = error "function called on too few arguments"
    applyAll w (x : xs) = applyAll (apply w x) xs

You can then write something like:

wrap3 (\x y z -> (x + y) * z) `applyAll` [9, 11, 2]

and it will rightly construct 40.

As you can tell, this involves a lot of baggage, but it's all necessary to tell the compiler "hey, this function is going to have three arguments so a list of length 3 is perfect for it" in a fully generic way.

Of course, writing applyAll (wrap3 ___) ___ is tedious. However, if you're trying to build a library of functions with arbitrary arities, you can probably work in some extra functions which manage those arities for you.

You may also want to annotate the length of your lists with Z, S Z, etc. -- in this case I think you can get an applyAll which does currying. Also, as another answer pointed out, you might be able to get a good distance with having multiple constructors for Wrap which move the recursion into the data type itself -- possibly being able to remove some of those nasty language extensions.

CR Drost
  • 9,637
  • 1
  • 25
  • 36
0

It's not possible to write truly arity-generic functions without dependent types or tons of extensions, but you can cheat a bit:

z :: b -> [a] -> b 
z x _ = x

s :: (b -> [a] -> c) -> (a -> b) -> [a] -> c
s h f (x:xs) = h (f x) xs
s _ _    _   = error "function called on too few arguments"

sum3 a b c   = a + b + c
tup4 a b c d = (a, b, c, d)

main = do
    print $ z               0    [1..4] -- 0
    print $ s (s (s z))     sum3 [1..4] -- 6
    print $ s (s (s (s z))) tup4 [1..4] -- (1, 2, 3, 4)
    -- print $ s (s (s (s z))) tup4 [1..3] -- runtime error

Or if you want to throw an error, if there are to many elements in a list, then change the definition of z to

z :: b -> [a] -> b 
z x [] = x
z _ _  = error "function called on too many arguments"
effectfully
  • 12,325
  • 2
  • 17
  • 40