Skip to content

Instantly share code, notes, and snippets.

@rehno-lindeque
Last active May 25, 2016 19:14
Show Gist options
  • Save rehno-lindeque/0fd273eaeab2ab2dc48c to your computer and use it in GitHub Desktop.
Save rehno-lindeque/0fd273eaeab2ab2dc48c to your computer and use it in GitHub Desktop.
Elm tuple proposal

Overview

Lately I've been annoyed that Haskell has tuples with more than 2 elements. It seems to me like a great deal of boilerplate in Haskell comes from writing code like Data.Profunctor.Product. One can find many, many more examples like this in Haskell libraries.

That is to say, suppose you could define types like this:

type alias A = (x,(x,x))

or:

type alias B = ((x,x),x)

but this were banned:

type alias Triple = (x,x,x)

would you lose anything important? After all, you could still do this manually instead:

type Triple' = Triple' x x x

This would free up (a,b,c) so that it could be used as syntax sugar instead:

(1,(2,3)) == (1,2,3)

Is this an idea that is worth exploring?

Possible objections

I haven't found the objections to this in say Why is (a,b,c,d) not sugar for (a,(b,(c,(d,()))))? and N-ary tuples vs pairs very compelling. The answer in the second link claims that it would weaken the type system. However, as I've demonstrated above, a manually defined type Triple' would easily recover anything that was lost. Furthermore, I'm not advocating any changes to the type system. The following inequality would remain:

(1,(2,3)) /= ((1,2),3)

so that

(1,2,3) /= ((1,2),3)

Instead, this would be a simplification since the equality (1,2,3) == (1,(2,3)) is enforced as syntax sugar rather than a type system extension.

Additional comments

Chaining binary functions that act on or construct tuples

This would make it possible to write much simpler code if Elm happened to get say bifunctors or arrows using an operator similar to (***):

type MyRecord =
  { a : String
  , b : Int
  , c : Int
  , d : [Int]
  , e : String
  }

let myTuple = ("Hello", "4", 8, [10.1, 9.3, 4.5], 'C')
in uncurry5 MyRecord 
     <| (identity *** fromString *** identity *** map ceiling *** toString)
     <| myTuple

...in the future. Unfortunately infix style functions bracket in the wrong direction though, so that bimap does not work...

type MyRecord =
  { a : String
  , b : Int
  , c : Int
  , d : [Int]
  , e : String
  }

let myTuple = ("Hello", "4", 8, [10.1, 9.3, 4.5], 'C')
in uncurry5 MyRecord 
     <| identity 
        `bimap` fromString
        `bimap` identity
        `bimap` map ceiling
        `bimap` toString
     <| myTuple
     -- Type error: (a,(b,(c,(d,e)))) does not match ((((a,b),c),d),e)

...never-the-less, I think at some point this sort of code will become desirable.

Generic programming

Also consider that something nearly recursive is now possible when it comes to defining functions like uncurry*:

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

uncurry4 : (a -> b -> c -> d -> e) -> (a,(b,(c,d))) -> e
uncurry4 f (a,bcd) = uncurry3 (f a) bcd

uncurry5 : (a -> b -> c -> d -> e -> f) -> (a,(b,(c,(d,e)))) -> f
uncurry5 f (a,bcde) = uncurry4 (f a) bcde

or with the syntax sugar applied to the types:

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

uncurry4 : (a -> b -> c -> d -> e) -> (a,b,c,d) -> e
uncurry4 f (a,bcd) = uncurry3 (f a) bcd

uncurry5 : (a -> b -> c -> d -> e -> f) -> (a,b,c,d,e) -> f
uncurry5 f (a,bcde) = uncurry4 (f a) bcde

I could imagine some future generic programming mechanism being significantly simpler as a result.

@TheSeamau5
Copy link

To make it more concrete, how should one represent a 4x4 matrix efficiently? I imagine a tuple is probably as efficient as it gets?

Yup, matrices are just typed arrays flattened to 1d.

Thing is that tuples are (from what I can tell) always JS objects. I think that this could be an optimization worth having. Like, an n-tuple of ints or floats are just typed arrays. Cuz then you could use destructuring to work with these things in Elm as opposed to like elm-linear-algebra use functions like getX and getY...

Imagine:

p = (1 ,2, 3)
q = (4, 0, 5)

(a,b,c) = p `add` q -- (5, 2, 8)

and that uses typed arrays. I wonder if you could push the optimization further to make records into typed arrays and somehow have the fact that the fields are called "x", "y", and "z" somewhere hidden in the runtime

@rehno-lindeque
Copy link
Author

I wonder if you could push the optimization further to make records into typed arrays and somehow have the fact that the fields are called "x", "y", and "z" somewhere hidden in the runtime

This would be great. I can't imagine that the Elm compiler would ever optimize away records' keys without some kind of prompting (in order to generate readable JS code), but perhaps it might generate more efficient/packed data structures for tuples.

@TheSeamau5
Copy link

I think that using tuples for math could be great given how common vector math is. It's sometimes in UI front-end work (with animations) but it's especially present with games, and people love writing games in Elm.

Given how common it is, we could super special case tuples for math operators. Like:

p = (1, 2, 3)
q = (3, 1, 2)

r = p + q -- (4, 3, 5)

We could have all the usual suspects when it comes to operators:

(+) : N-Tuple number -> N-Tuple number -> N-Tuple number
(-) : N-Tuple number -> N-Tuple number -> N-Tuple number
(*) : N-Tuple number -> N-Tuple number -> N-Tuple number
(/) : N-Tuple number -> N-Tuple number -> N-Tuple number
(.) : N-Tuple number -> N-Tuple number -> N-Tuple number

cross : 3-Tuple number -> 3-Tuple number -> 3-Tuple number

and we could also extend them to matrices where matrices are nested tuples.

Like:

type alias Matrix4 = 
  ( (number, number, number, number)
  , (number, number, number, number)
  , (number, number, number, number)
  , (number, number, number, number)
  )

and until the day Elm gets something like Typeclasses (if it ever gets it), the compiler treats these math operators the same way ++ is treated. Like they're super special and they can work on a few types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment