Stream: ideas

Topic: tuple syntax


view this post on Zulip Richard Feldman (Jan 07 2022 at 15:01):

following up on https://roc.zulipchat.com/#narrow/stream/231634-beginners/topic/syntax/near/259018434 - an idea I'd like to discuss here is this: what if Roc had tuple syntax where tuples were syntax sugar for a tag called Tuple?

so for example:

the type (Str) and the expression (a) would continue to not be tuples, as there's no point in having a 1-tuple anyway, and we wouldn't add () since that would be redundant with {} anyway

view this post on Zulip Richard Feldman (Jan 07 2022 at 15:01):

love to get anyone's thoughts on that idea!

view this post on Zulip Richard Feldman (Jan 07 2022 at 15:07):

I originally had the idea that we could use something a tag like Pair or Tuple directly, or record destructuring, but it seems in practice that a lot of use cases that use tuple in other languages - most recently Random.step in Elm - it's significantly less nice not having tuple syntax, so I'd like to talk about the idea of adding it

view this post on Zulip Anton (Jan 07 2022 at 15:29):

This missing syntax is something I wondered since I first saw Roc. ( a, b, c) is very clean, convenient and I can't think of any significant downsides.

view this post on Zulip Brian Carroll (Jan 07 2022 at 16:03):

Yeah I think this would be great! It really feels like a case where a little bit of syntax sugar will make a big difference to how comfortable it is to use.

view this post on Zulip Zeljko Nesic (Jan 07 2022 at 19:55):

I love tuples, and they are really helpful to circumvent a lot of ceremony to get stuff working, and I vote Aye for them! :)

view this post on Zulip Richard Feldman (Jan 07 2022 at 20:28):

well, this seems like a polarizing one , lot of different viewpoints... :laughing:

view this post on Zulip Richard Feldman (Jan 07 2022 at 20:28):

anyone opposed to the idea?

view this post on Zulip Lucas Rosa (Jan 07 2022 at 21:50):

lol I also think it's a great idea. It's not really magic or anything and very very convenient. I suspect a lot of people would want this

view this post on Zulip Martin Stewart (Jan 07 2022 at 22:07):

Sorry, I'm going to be a contrarian (again) and also bring up Elm (again again) :sweat_smile: .

When using Elm I find that I don't often use tuples and instead stick to records for clarity. Also tuples add an extra burden when making elm-review rules or when developing tools. That said, if the issue is verbosity, couldn't one write T value0 value1 instead? There's of course a little gain with having tuple syntax since you don't need to add parens around value0 and value1 if they happen to be expressions but I don't think that's enough to justify tuple syntax.

That being said, Roc isn't 100% like Elm so maybe I'm misunderstanding this pain point. I need to get around to setting up Roc one of these days so I can play around with it...

view this post on Zulip Joshua Warner (Jan 07 2022 at 22:16):

I've seen cases in rust codebases where tuple return types are _way_ over-used. e.g. cases with 5+ elements in the tuple and where code readability was really improved by swapping that for a real struct.

IMO tuples should be a pretty niche feature, and anything beyond pairs is a bit of a code smell.

view this post on Zulip jan kili (Jan 07 2022 at 22:57):

I'm positive on tuples, but would they support partial destructuring? Record destructuring feels great, but I notice that when destructuring tags, _ is not allowed (or sometimes breaks the parser) for any of the payloads, and it would be annoying to have to use every element of a tuple. I'm not sure if that tag behavior is by working as intended, though.

{ a, b } = x
vs.
{ a } = x

Tuple a b = y
vs.
Tuple a _ = y

( a, b ) = z
( a, _ ) = z

view this post on Zulip Richard Feldman (Jan 07 2022 at 23:40):

_ should definitely work in tag destructuring! It would too in a hypothetical tuple syntax

view this post on Zulip Richard Feldman (Jan 07 2022 at 23:41):

@Joshua Warner incidentally, Elm only allows 2-tuples and 3-tuples to discourage going overboard with them

view this post on Zulip Zeljko Nesic (Jan 08 2022 at 00:02):

I use tuples mostly in Haskell: when you are folding over some structure, and besides accumulating data there is some auxiliary helper data that you need - it is much more convenient to wrap those two in a tuple, than to declare some trivial used-once datatype.

view this post on Zulip Brendan Hansknecht (Jan 08 2022 at 03:07):

So the entire difference would be (a, b, c) instead of (T a b c), right? Cause T would be the minimal tag definition for a tuple?

view this post on Zulip Richard Feldman (Jan 08 2022 at 03:21):

correct!

view this post on Zulip Locria Cyber (Jan 08 2022 at 11:38):

So this is syntax sugar. ( a, b ) uses comma round brackets for tuples, and comma are used for arguments, and round brackets are used to override order of operations. Maybe the use of comma here will break other usage.

There can be an operator for creating tuple. Then again, the type of the operator will be different.

(a <+> b) the type of operator is T, T -> [ Tuple T T ]

(a <+> b <+> c) the type of the last operator is [ Tuple T T ], T -> [ Tuple [Tuple T T] T ]

view this post on Zulip Zeljko Nesic (Jan 08 2022 at 17:50):

That is awesome argument actually _against_ tuples :D

view this post on Zulip Zeljko Nesic (Jan 08 2022 at 17:53):

Also counter my argument for ease of use of tuples:
Roc does have on the fly tags, so there is no need for extra bureaucracy around it. I am always allowed to wrap stuff up in something made on the fly

x = List.walkLeft (\(Acc acc aux) item ->
  Acc (mumboJumbo acc (jumboMumbo aux))
)

and never mentioning what is Acc actually consisting of.

view this post on Zulip Zeljko Nesic (Jan 08 2022 at 17:56):

So I am turning around, lets not be like Elm, lets be more like ... Ruby? :D

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:18):

so a use case that @JanCVanB and I were looking at the other day was random number generation, and specifically a function like step in elm/random

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:19):

comparing what Elm does (the first bullet) to some alternatives that are supported in Roc today:

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:20):

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:20):

then there's the question of what it would look like to use it

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:21):

comparing an Elm version (first bullet) to some alternatives that are supported in Roc today:

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:22):

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:22):

how do people feel about each of those options?

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:29):

what others are worth considering? (I was trying Out and Ret as riffs on @Zeljko Nesic's Acc in the example above - choosing a tag name that's more tied to its role in this specific function, as opposed to something generic like "tuple" - maybe there are other names worth trying in this case?)

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:30):

I can also see an argument that the version which returns a record is best, even though it's the most verbose, since it means you don't have to remember the argument order...but of course if you forget the argument order you'll definitely get a type mismatch unless somehow you were generating Seeds :stuck_out_tongue:

view this post on Zulip Martin Stewart (Jan 08 2022 at 18:39):

T2, Tuple, and Pair all sound like good names to me.

That said, presumably we'll want helper functions like Tuple.first: Tuple a * -> a and Tuple.second: Tuple * a -> a which means we'll need to be consistent about what name we use for tuples? If the Random module is using Output because it's returning Output a Seed from a function then users won't able able to use those tuple helper functions with Output a Seed, right?

Assuming I haven't misunderstood here, maybe this is evidence for just using records so there isn't a need to coordinate what tuples should be called?

view this post on Zulip Anton (Jan 08 2022 at 18:43):

The record and the python tuple ( a, Seed) stand out, which I like, because otherwise there is a lot of space-separated stuff you need to inspect a little more before you know what's what. It's hard to make a decision without writing a lot of code in multiple tuple styles.

view this post on Zulip Anton (Jan 08 2022 at 18:45):

For these Random functions I'd go for the record

view this post on Zulip Richard Feldman (Jan 08 2022 at 18:59):

presumably we'll want helper functions

I actually don't want to take that as a given. I know Elm and Haskell have them, but Rust doesn't, and it seems to be fine...so I'd like to see if we can get away with not needing them! :big_smile:

view this post on Zulip Matthias Devlamynck (Jan 09 2022 at 09:15):

To be fair rust has field access for tuple so helpers tend to be less useful :

let tuple = (1, 2);
assert_eq!(1, tuple.0);

Does Roc have field getter functions like elm? For a record { id : Int } having the function .id : { id : Int } -> Int. If so maybe the rust syntax could be a good candidates tuple : .0, .1, … (but then we would need these to be generic over the size of the tuple, so maybe it's a bad idea)

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:42):

we do have the field getter functions, yeah (which reminds me, I need to add them to the tutorial)

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:42):

I hadn't thought about .0, .1 etc.

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:43):

if tuples are syntax sugar for tags, then .0, .1 etc would have to work with tag unions - which is actually possible

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:44):

like (Point3d 5 6 7).1 could evaluate to 6

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:47):

I was gonna say it would have to only work on tag unions with a single tag, but theoretically it could work on unions with multiple tags as long as they all had payloads with the exact same type at that position :stuck_out_tongue:

view this post on Zulip Richard Feldman (Jan 09 2022 at 12:49):

a counterpoint to this direction is that records already support all this already, in a simpler way and likely with nicer error messages, so maybe it's actually an argument for choosing records over tuples

view this post on Zulip jan kili (Jan 09 2022 at 13:34):

If we choose the convention to be "use a record or a tag", then that is a conscious choice to increase the variety of one-off field/tag names. This may be a good thing, to force package developers to name things, but we should expect to see every option mixed together in one app: Tuples next to T2s next to value first second fst snd f s Acc Ret and Stuff

view this post on Zulip Johannes Maas (Jan 09 2022 at 13:56):

Are there other use cases for tuples other than bunching together values for returning (or passing) them together?

view this post on Zulip Richard Feldman (Jan 09 2022 at 13:57):

the only other one I can think of is in conditionals

view this post on Zulip Johannes Maas (Jan 09 2022 at 13:58):

And I find destructuring records inconvenient, when I want to rename a field. With tuples, you just place a variable at the position, but you'd need extra syntax for renaming a field, which isn't obvious so I tend to forget how it's done.

Though that is a minor point, nothing too important.

view this post on Zulip Richard Feldman (Jan 09 2022 at 13:58):

e.g. in Elm I'd often write the equivalent of:

when ( result1, result2 ) is
    ( Ok a, Ok b ) -> ...
    ( _, _ ) -> ...

view this post on Zulip Johannes Maas (Jan 09 2022 at 13:58):

True, so it's about grouping multiple values to fit into a slot that technically only accepts one.

view this post on Zulip Richard Feldman (Jan 09 2022 at 13:58):

right

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:00):

could do it with a record:

when { r1: result1, r2: result2 } is
    { r1: Ok a, r2: Ok b } -> ...
    _ -> ...

...but then it's a bit annoying that you have to pick names for the fields that aren't really adding value

view this post on Zulip Johannes Maas (Jan 09 2022 at 14:00):

And now I see tuples as anynomous records. Just like lambdas are functions where you don't care about the name.

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:00):

could also do it with a tag of course:

when Pair result1 result2 is
    Pair (Ok a) (Ok b) -> ...
    Pair _ _ -> ...

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:01):

yeah pretty much!

view this post on Zulip Johannes Maas (Jan 09 2022 at 14:02):

Just omitting the field names in records might lead to issues when you mix named and anonymous fields. So that's probably why we tend to have a separate syntax for fully anonymous records aka tuples.

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:03):

oh yeah, { x, y } already means something :big_smile:

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:04):

but yeah, functionally tuples and tag payloads do the same thing: they give you positional field access instead of named field access

view this post on Zulip Richard Feldman (Jan 09 2022 at 14:04):

it's really just a matter of syntax (or at least it would be with the proposed design)

view this post on Zulip Johannes Maas (Jan 09 2022 at 14:06):

I think I agree with the previous statement that the parens and commas make it easier to read than the space-separated tuples, so it would be worth it.

And having a canonical tag name for tuples might also be beneficial, right?

view this post on Zulip Zeljko Nesic (Jan 09 2022 at 15:20):

I like the idea of forcing users to make constructs ala StrAndInt for something short that they might need because that is the only way that you pack data up in Roc.

On the other hand, i might imagine disgust it might provoke among newcomers to the language.

view this post on Zulip Matthias Devlamynck (Jan 09 2022 at 16:23):

Tuple could also be sugar for records with numbers instead of names for fields(i.e. (1, True, "string") : (Int, Bool, String) would be sugar for { 0: 1, 1: True, 2: "string" } : { 0 : Int, 1 : Bool, 2 : String }). The drawback is that number fields would only be valid for tuple which makes the design a bit inconsistent.

view this post on Zulip Richard Feldman (Jan 09 2022 at 16:30):

interesting! I hadn't thought of that design. :thinking:

What do others think of that idea?

view this post on Zulip Matthias Devlamynck (Jan 09 2022 at 16:31):

(also it kind of bridge the gap conceptually for the unit type, i.e. () = {})

view this post on Zulip Richard Feldman (Jan 09 2022 at 16:32):

would there be any benefit to introducing () to the language? I've never been able to think of any significant ones :big_smile:

view this post on Zulip Matthias Devlamynck (Jan 09 2022 at 16:34):

Can't see any, I only meant that tuples and records are basically the same thing, only the naming of fields differs.

view this post on Zulip Richard Feldman (Jan 09 2022 at 16:34):

one downside of the "tuples are sugar for records with numbers for fields" idea, compared to the "tuples are sugar for a Tuple tag" idea is that you can no longer do List.map3 list1 list2 list3 Tuple thing

view this post on Zulip Richard Feldman (Jan 09 2022 at 16:36):

to support that, there'd have to be special syntax for that like (,,) in Elm

view this post on Zulip jan kili (Jan 09 2022 at 16:50):

Random thought- if implemented to it's logical conclusion, that could have some fun destructuring:

a = (1, 2, 3)
(b, c, d) = a
{ 2: last } = a
f = \{ 9: target } -> target

Eh, tuple-style destructuring is probably superior.

view this post on Zulip Ayaz Hafiz (Jan 09 2022 at 17:46):

I like the idea of representing them as special records more than tags. Two reasons for this:

  1. I think it could be confusing for a beginner (and even someone who leaves the language for a bit, then comes back) to remember that Tuple a b == (a, b). In particular, the direction of having to remember Tuple a b can be swapped for (a, b) at any time seems a bit magical. IMO having the unidirectional desugaring of (a, b) to {0: a, 1: b} is better because it has all the same semantics present in other parts of the language, but you can't ever create a {0: a, 1: b} directly, so tuples exist in their own world.

  2. I worry about how the use of the specialized Tuple tag may play with other constructs. For example, if I have

TwoOrThree a : [Tuple a a, Triple a a a]

I would now be permitted to do something like (suppose + is defined for a, via abilities or something else)

add : TwoOrThree a -> a
add = \t -> when t is
  (a, a) -> a + a
  Triple a a a -> a + a + a

Which is a little bit hard to read IMO, I would expect the shapes of everything I'm destructuring to look the same.
One way to avoid this issue in particular is to make Tuple a special tag in the sense that it can't be used as part of a larger tag union data structure. In that model when I do something like

add = \t -> when t is
  Tuple a a -> a + a
  Triple a a a -> a + a + a

The error message would either have to say that "Triple" can't be returned because only a "Tuple" can come out of this function, or that you should rename "Tuple" to something else. IMO this is something that may catch folks relatively often

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:48):

ha, that's a good point!

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:49):

well, both are good points :big_smile:

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:52):

the [ Tuple a a, Triple a a a ] makes me think of an interesting consideration: if tuples are syntax sugar for records where the field names are numbers, then I can do this:

recordIdentity : {}a -> {}a
recordIdentity = \rec -> rec

recordIdentity ( 1, 2, 3 )

...but I actually can't mix tuples and open records other than the empty record (e.g. pass a tuple to an argument expecting { a : Str }*) because for that to work, I have to provide at least one named field, and tuples can't do that

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:53):

however, we'd probably have to explicitly rule this case out:

tuple = ( 1, 2, 3 )

record = { a: "string", b: "string" }

mixed = { record & tuple }

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:54):

otherwise mixed would have both named fields and numbered fields :stuck_out_tongue_wink:

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:54):

but of course if we rule that out, then tuples are no longer syntax sugar for plain records anymore :laughing:

view this post on Zulip Richard Feldman (Jan 09 2022 at 17:55):

(although they'd still be similarly easy to teach: "Tuples work just like records except they use numbers for fields instead of names. Also you can't use & with them.")

view this post on Zulip Zeljko Nesic (Jan 09 2022 at 18:29):

To me that reads much like some piece of Javascript spec :D

IMO "tuples are records with number for fields" looks like a dirty idea. It feels like a ocean of features is beneath your feet.

view this post on Zulip Johannes Maas (Jan 10 2022 at 17:10):

Maybe it's good to try leaving it out at first and adding it in once people complain about specific issues. :grinning_face_with_smiling_eyes:

view this post on Zulip Brian Carroll (Jan 10 2022 at 17:54):

That's actually been the strategy for a while already! I think earlier in the thread, Richard mentioned that some use cases have been cropping up so it's time to consider whether it's time to just go for it.

view this post on Zulip Richard Feldman (Jan 10 2022 at 23:26):

yeah exactly :big_smile:

view this post on Zulip Richard Feldman (Jan 10 2022 at 23:26):

I'm ok with saying we should continue with the "wait and see" approach

view this post on Zulip Richard Feldman (Jan 10 2022 at 23:28):

also it seems like we should try defaulting to records whenever we'd otherwise reach for tuples in an API (as opposed to locally in a when or something) and see how it feels

view this post on Zulip Joseph Anthony Zullo (Jan 11 2022 at 15:56):

Someone mentioned that tuples are like anonymous records. This is kind of true since tuples are anonymous, but when you discuss anonymous records I think most people would be thinking about records with row types, which are different.

Having a unit literal could still be useful when working with polymorphic values when a value isn't being used. It's also useful for thunking, infinite lists, chunking, etc. Maybe these cases don't come up often enough since defining a Unit tag is also sufficient.

I would also not have the tuple be syntactic sugar for anything and just be it's own thing. Tuple constructors of different arity should all be named differently otherwise so they don't interfere with each other.

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:02):

Brendan reminded me of an earlier "tuples as syntax sugar" design we talked about:

(a, b, c) is syntax sugar for Tuple a b c

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:03):

this has the advantage that you can do things like List.map2 list1 list2 Tuple to get back a list of tuples, where each tuple has one element from list1 and one element from list2

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:05):

unlike the "tuples are syntax sugar for records" design, this wouldn't enable .1 and .2

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:06):

.1 and .2 either require that tuples desugar to records, or else that they aren't sugar and have their own seeparate type system rules

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:07):

because it has to be possible to infer the type of \x -> x.2 and that function also has to accept tuples with more than 3 elements (so it can't be sugar for \Tuple _ _ a -> a because that would only work for tuples of exactly 3 elements)

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:11):

one reason I like the .1 and .2 functions is that otherwise there's inevitably demand for convenience functions like Tuple2.first, Tuple2.second, Tuple3.first, Tuple3.second, Tuple3.third etc.

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:11):

one possible design we haven't talked about is to make tuples "extensible" like records, but not have update syntax

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:12):

in other words, the type of \x -> x.2 is (*, *, a)* -> a

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:12):

just like how \x -> x.foo would be inferred as { foo : a }* -> a

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:14):

it might literally only come up in exactly that situation though

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:14):

because you'd probably want pattern matches on tuples to be closed, e.g. (x, y) = foo should give a type mismatch if foo is a 3-tuple

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:14):

even though it could be designed to Just Work, I think for tuples it's less error-prone to give an error for a pattern match like that

view this post on Zulip Brendan Hansknecht (Jul 13 2022 at 01:18):

As long as users don't make overly large tuples, this syntax should be pretty terse:

(_, _, _, _, a, _, _) = foo

That is with a 7 long tuple (which probably should be a record) and is still pretty similar in length to a = Tuple7.Fifth foo

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:50):

true, although the reason I usually see demand for those helper functions is to use them in pipelines and higher-order functions

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:50):

e.g. you have a list of tuples and you want to extract the first element, and you do List.map tuples Tuple2.first

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:50):

or if there's syntax for it, List.map tuples .0

view this post on Zulip Richard Feldman (Jul 13 2022 at 01:51):

similarly, in a pipeline:

tuples
|> List.first
|> Result.map .0

view this post on Zulip Brendan Hansknecht (Jul 13 2022 at 02:04):

That makes sense. Not something I would ever think up, but looks really nice. Need to keep getting more used to functional programming patterns.

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:36):

I don't know how I missed this, but there's a pretty nice design opportunity here if tuples are sugar for single-tag unions:

(1, 2) == Tuple 1 2
List.map2 [1, "a"] [2, "b"] Tuple
    == [(1, "a"), (2, "b")]
(42, "x").1 == "x"

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:37):

and then the type of the .1 function is just [Tuple * a] -> a

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:38):

oh, hm that has the problem of .1 not working on longer tuples :thinking:

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:41):

ok I guess they do need to be their own thing :stuck_out_tongue:

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:51):

the reason I was thinking about this is that it seemed like it could also be nice to have .1 etc work on single-tag unions

view this post on Zulip Richard Feldman (Oct 18 2022 at 10:52):

but maybe there's no need to make those anymore if tuples exist

view this post on Zulip jan kili (Oct 18 2022 at 11:15):

Would it make sense to introduce a second special payload type ** that enables [Tuple * a **] matching on any Tuple tag with two or more payloads? That could save this latest proposal, but I imagine it would also solve/create various other problems.

view this post on Zulip Anton (Oct 18 2022 at 11:33):

I'd say that adds more complexity/confusion than the feature would take away.

view this post on Zulip witoldsz (Oct 19 2022 at 19:55):

I would like to add, that for 2 years I'm writing BLOBAs in F# which is now my favorite languge (just after ROC of course :) and during that time I had to use function like fst or snd on 3-elements-tuple only ONCE. F# has not such functions for 3- and more tuples, so I've implemented it ad-hoc (one liner) just to make a pipeline process easier to read.

My point is, I would sacrifice all those .0...n superb accessors for any-dimensional tuples and KISS. Tuples are a must have in my opinion, but pattern matching + fst/snd for binary tuples are just fine!

P.S. BLOBAs are boring, line of business applications ;)

view this post on Zulip Brendan Hansknecht (Oct 19 2022 at 19:57):

I think the main reason for .1 to .n is that tuples are theoretically how you would implement an array. Which is important for performance in some cases. Though I guess a record works too, just more tedious.

view this post on Zulip witoldsz (Oct 19 2022 at 20:07):

One more thing I would like to add is how cool are tuples implemented in F#, so that most of the time you can omit the brackets. It makes them look so "lightweight", comparing with eg. Elm which is btw. also my favorite language ;)

I won't provide any examples as I'm on my phone now, but please, take my word for the bracket-less tuples :)

view this post on Zulip Richard Feldman (Oct 19 2022 at 20:57):

hm, I'm not seeing it in https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/tuples :thinking:

view this post on Zulip Ayaz Hafiz (Oct 19 2022 at 21:00):

https://fsharpforfunandprofit.com/posts/tuples/#making-and-matching-tuples has an example

view this post on Zulip Richard Feldman (Oct 19 2022 at 21:07):

neat!

view this post on Zulip Richard Feldman (Oct 19 2022 at 21:07):

I briefly thought about trying something like this once, but I assumed it would be confusing - apparently it's already been tried and is not confusing in practice?

view this post on Zulip Ayaz Hafiz (Oct 19 2022 at 21:24):

OCaml has the same thing, but personally I do find it confusing, because depending on the context you may or may not need tuples

view this post on Zulip Ayaz Hafiz (Oct 19 2022 at 21:24):

It would be the same in Roc, for example when defining a list literal of tuples vs appending a tuple to a list

view this post on Zulip Richard Feldman (Oct 19 2022 at 22:05):

yeah I guess in the list literal case you'd have to use the parens

view this post on Zulip Richard Feldman (Oct 24 2022 at 18:04):

interesting observation: if we have "extensible tuples" then we can actually have a Tuple module that's more useful than normal, e.g we can do things like

Tuple.mapFirst : (a)t, (a -> b) -> (b)t

Tuple.mapSecond : (a, b)t, (b -> c) -> (a, c)t

Tuple.mapThird : (a, b, c)t, (c -> d) -> (a, b, d)t

view this post on Zulip Richard Feldman (Oct 24 2022 at 18:04):

and you could call those on any tuples that were at least as long as requested

view this post on Zulip Richard Feldman (Oct 24 2022 at 18:05):

e.g. you could call mapFirst on a 2-tuple, 3-tuple, 4-tuple, etc.

view this post on Zulip Ayaz Hafiz (Oct 24 2022 at 18:07):

Was just thinking the same things :sweat_smile: https://github.com/roc-lang/roc/pull/4358#discussion_r1003607952


Last updated: Jun 16 2026 at 16:19 UTC