4

I commonly have a "oh yeah" moment writing F# when I realize I need an extra value somewhere. This is generally easily done by adding another value to the tuple being passed around. However, this means that various maps/sorts/collects/etc. need updating, and in particular the functions fst/snd only work on tuples of length 2.

It's not a huge issue, but it's annoying enough during exploratory development that I though I'd write a helper to alleviate the annoyance:

let inline get2 (t:^a) = (^a : (member get_Item2 : unit -> string) (t, ()))
let inline get2a (t:^a) = (^a : (member Item2 : string) t)

However, both versions do not work. The first, get2, won't compile, with "Expected 1 expressions, got 2". The second, get2a, will compile, but subsequently can't be used on tuples: "The type '(int * string)' does not support any operators named 'get_Item2'".

Is there any way of doing this that doesn't involve lots of overloads? with noisy OverloadID annotations (annotations not required in F# 2.0)

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166

2 Answers2

8

The reason why ItemX static constraints on F# tuples do not work is because System.Tuple<_,...,_> is only the encoded form of tuples, and not the static representation used by the compiler. See 6.3.2 Tuple Expressions in the specification.

However, with a little work, you can obtain the runtime encoding of a given tuple like so:

open System
//like get2a but generic return type
let inline get2b (t:^a) = (^a : (member Item2 : 'b) t)

let x = (1,2)
let y = (1,2,3)

get2b (box x :?> Tuple<int,int>)
get2b (box y :?> Tuple<int,int,int>)
Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
3

You can do it using reflection:

let inline get (t:^a) = t.GetType().GetProperty("Item2").GetValue(t,null) :?> string

Also, I would suggest that tuples are not really a great data structure for passing data around, they may be useful inside a function for small operations, but in case there is a frequent change in the structure, tuple can be really painful to work with

Ankur
  • 33,367
  • 2
  • 46
  • 72
  • I have tried doing what Eamon wants a while back, and if I remember right - your solution is the only. – Ramon Snir Jul 28 '11 at 09:56
  • The whole point is to do this during exploratory development; I don't have types defined yet because they're changing all the time. So this kind of thing would be purely local but using explicit records is less ideal since that means changing their definition (and indeed needing to have a definition) which I'm likely to redo anyhow once the code starts shaping up. – Eamon Nerbonne Jul 28 '11 at 10:25
  • Ok, then I think using reflection in case of exploratory dev wont be much of an issue with respect to performance – Ankur Jul 28 '11 at 10:31
  • 2
    Using reflection to get the arbitrary nth element of a tuple: http://stackoverflow.com/questions/5252628/how-can-i-refer-to-a-specific-member-of-a-tuple-of-any-size-in-f/5255181#5255181 – Stephen Swensen Jul 28 '11 at 13:19
  • Performance is an issue - if it's not snappy, it's not useful as an exploratory tool. I'd have to measure it to find out whether the slowdown is significant, however. – Eamon Nerbonne Jul 29 '11 at 08:07
  • 1
    hmm, and it looks like reflection will break type-inference; I guess long lists of overloads it is. – Eamon Nerbonne Jul 29 '11 at 08:13
  • 2
    @Eamon Nerbonne - unfortunately, overloads will break type-inference as well: http://fssnip.net/6V – Stephen Swensen Jul 29 '11 at 13:17
  • So a general `fst` function is impossible in F#; I guess? – Eamon Nerbonne Jul 30 '11 at 06:23
  • It is not just F# case, it will be same for any statically typed language – Ankur Jul 30 '11 at 15:43
  • 1
    It's possible in C++ for example, and Haskell's type classes would make something similar possible too. Even plain overloads *work*, and type inference is possible, just not performed. Really, it's trivial to reason about statically, so I don't see what being a statically checked language has to do with it - it's just not implemented, apparently. – Eamon Nerbonne Aug 24 '11 at 13:07