Hacker News new | ask | show | jobs
by Tyr42 2148 days ago
Haskell's not too bad once you understand ZipList

http://learnyouahaskell.com/functors-applicative-functors-an...

    max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]  
    > [5,3,3,4]
2 comments

Yeah it's not at all complicated in Haskell. I'm not sure what GP is talking about.
I replied to the parent as well, but not only is the solution the parent showed significantly more complex than the CL version, I'm not even sure it actually does what I asked.

More explicitly, the expression there seems to rely on knowing the arity of the function and the number of lists at compile time. Basically, I was asking for a function cl_map such that:

    cl_map foo [xs:[ys:[zs:...]]] = foo <$> xs <*> ys <*> zs <*> ...
Edit: found a paper explaining that this is not possible in Haskell, and showing how the problem is solved in Typed Scheme: https://www2.ccs.neu.edu/racket/pubs/esop09-sthf.pdf
Sure it's possible in Haskell. I'm not sure where in that paper you got the impression it isn't. Of course one can't define variadic functions in Haskell, but that's a more fundamental difference from Clojure, not a "code pattern that [is] safe and easy to do with dynamic typing, but impossible with simple type systems or more complex with more advanced type system."

    > traverse_ print (sequenceA [ZipList [1,2], ZipList [3,4]])
    [1,3]
    [2,4]
As far as I can tell, your example calls a unary function on each element of a list of lists. It's solving the variadic part of map, but not the part where I can call an N-ary function with each element of N lists.

Basically, instead of your example I would like to do something like this:

    > cl_map (+) [ZipList [1,2,3], ZipList [4,5,6]]
    [5,7,9]

    > cl_map (+ 3) [ZipList [1,2,3]]
    [4,5,6]

    > cl_map max3 [ZipList [1,2], ZipList [3,4], ZipList [5,6]] where max3 x y z = max x (max y z)
    [5, 6]
Can this be done? What is the type of cl_map?

Note: If this doesn't work with ZipList, that's ok - the important part is being able to supply the function at runtime. Also, please don't assume that the function is associative or anything like that - it's an arbitrary function of N parameters.

The functions in those examples have fixed numbers of arguments, so one would use the original formulation shown by Tyr42.

    > (+) <$> ZipList [1,2,3] <*> ZipList [4,5,6]
    ZipList {getZipList = [5,7,9]}

    > (+3) <$> ZipList [1,2,3]
    ZipList {getZipList = [4,5,6]}

    > let max3 x y z = max x (max y z)
    > max3 <$> ZipList [1,2] <*> ZipList [3,4] <*> ZipList [5,6]
    ZipList {getZipList = [5,6]}
If you want to use "functions unknown at runtime that could take any number of arguments" then you'll have to pass the arguments in a list. Of course these can crash at runtime, which Haskellers wouldn't be happy with given an alternative, but hey-ho, let's see where we get.

    > let unsafePlus [x, y] = x + y
    > fmap unsafePlus (sequenceA [ZipList [1,2,3], ZipList [4,5,6]])
    ZipList {getZipList = [5,7,9]}

    > let unsafePlus3 [x] = x + 3
    > fmap unsafePlus3 (sequenceA [ZipList [1,2,3]])
    ZipList {getZipList = [4,5,6]}

    > unsafeMax3 [x, y, z] = x `max` y `max` z
    > fmap unsafeMax3 (sequenceA [ZipList [1,2], ZipList [3,4], ZipList [5,6]])
    ZipList {getZipList = [5,6]}
So the answer to your question is that

    cl_map :: ([a] -> b) -> [ZipList a] -> ZipList b
    cl_map f = fmap f . sequenceA
except you don't actually want all the elements of the list to be of the same type, you want them to be of dynamic type, so let's just make them Dynamic.

    > let unwrap x = fromDyn x (error "Type error")
    >
    > let unsafeGreeting [name, authorized] =
    >    if unwrap authorized then "Welcome, " ++ unwrap name
    >                         else "UNAUTHORIZED!"
    >
    > fmap unsafeGreeting (sequenceA [ZipList [toDyn "tome", toDyn "simiones", toDyn "pg"]
    >                               , ZipList [toDyn True,   toDyn True,       toDyn False]])
    ZipList {getZipList = ["Welcome, tome","Welcome, simiones","UNAUTHORIZED!"]}
and the type of cl_map becomes

    cl_map :: ([Dynamic] -> b) -> [ZipList Dynamic] -> ZipList b
    cl_map f = fmap f . sequenceA
One could polish this up a bit and make a coherent ecosystem out of it, but Haskell programmers hardly ever use Dynamic. We just don't come across the situations where Clojurists seem to think it's necessary.
So in the end, as I claimed initially, this function can't be written in a simple, safe way in Haskell; and as the article I linked claims, Haskell's type system can't encode the type of the cl_map function.

It's nice that Haskell does offer a way to circumvent the type system to write somewhat dynamic code, but it's a shame that in order to write a relatively simple function we need to resort to that.

Note that the type of cl_map is perfectly static. It would be `Integer N => (a_0 ->... a_N -> r) -> [a_0] ->... [a_N] -> [r]` assuming some fictitious syntax.

I will start by saying it took me a while to even parse the expression you provided. Whoever thought that inventing new operators is a way to write readable code should really be kept far away from programming languages. The article you provided didn't even bother to give a name to <*> and <$> so I could at least read them out to myself.

Anyway, bitter syntax sugar aside, the way you wrote the function I proposed was... a completely different function with similar results, which does not have the type I was asking for, and you only had to introduce 2 or 3 helper functions and one helper type to do it. I wanted to work with functions and lists, but now I get to learn about applicatives and ZipLists as well... no extra complication required!

Edit to ask: could this method be applied if you didn't know the number of lists and the function at compile time? CL's map would be the equivalent of a function that produces the expression you have showed me, but it's not clear to me that you could write this function in Haskell.

Edit2: found a paper explaining that this is not possible in Haskell, and showing how the problem is solved in Typed Scheme: https://www2.ccs.neu.edu/racket/pubs/esop09-sthf.pdf