Skip to content

Instantly share code, notes, and snippets.

@tmspzz
Last active June 2, 2017 19:26
Show Gist options
  • Save tmspzz/53f9568617654c38a219dd4a8353d935 to your computer and use it in GitHub Desktop.
Save tmspzz/53f9568617654c38a219dd4a8353d935 to your computer and use it in GitHub Desktop.
Add Tuple unsplatting - Removed by SE-0110

Add Tuple unsplatting

As a side effect of SE-0110 tuple unsplating was removed from the language.

While this claims to make tooling like the type checker faster, it deals quite a blow to expressivity.

Filterting dictionaries is just an example and maybe not the best. However I hope the point gets across. Let's compare to other languages.

Examples of pattern matching in closure parameters:

Swift

let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter { arg in
  let (_, age) = arg // Awkward :| No tuple unsplatting
  return age >= 18
}

// OR

// Still No tuple unsplatting
let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter { $0.1 >= 18 } 

// OR

// Somewhat better
let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {(arg: (name: String, age: Int)) in arg.age >= 18 }

Kotlin

mapOf("Tom" to 33, "Rebecca" to 17, "Siri" to 5).filter({ (_, age) -> age >= 18 })

Python 2.7

{name: age for name, age in {'Tom': 33, 'Rebecca': 17, 'Siri': 5}.iteritems() if age >= 18 }

Ruby

{"Tom" => 33, "Rebecca" => 17, "Siri" => 5}.select{ |_, age| value >= 18 }

Haskell

filter (\(_,age) -> age>= 18) [("Tom", 33), ("Rebecca", 17), ("Siri", 5)]

Clojure

(filter (fn [[_ age]] (>= age 18)) {"Tom" 33 "Rebecca" 17 "Siri" 5})

Scala

List(("Tom" , 33), ("Rebecca", 17), ("Siri", 5)).filter{ case (_, age) => age >= 18}

Rust

[("Tom", 33), ("Rebecca", 17), ("Siri", 5)].into_iter().filter(|&&(_, age)| age >= 18);

JavaScript

Object.entries({Tom: 33, Rebecca: 17, Siri: 5}).filter(([_, age]) => value >= 18)

Erlang

[T || {_, Age} = T <- [{"Tom", 33}, {"Rebecca", 17}, {"Siri", 5}], Age > 18].

CLisp

(remove-if-not #'(lambda (x) (>= (cadr x) 18)) '(("Tom" 30) ("Rebecca" 17) ("Siri" 5)))
@AliSoftware
Copy link

i don't think making tooling easier at the expense of the people who use tools is the right tradeoff. Especially when this is not the case in other languages.

@blender: Again:

  1. Where you think other languages offer that magic-unsplatting possibility, I don't think it's the case for all and I think you misinterpret the availability of dedicated function overloads on Hash/Dictionary for these languages as magic unsplatting while it's just a proper definition of those methods on Hash which takes 2 arguments (and not one single tuple arg)
  2. The tooling and the state before SE-110 was already at the expense of the users of the tooling — because it was one of the reasons why the type checker was slow. If you imagine that we have SE-110 which allows a speed increase on the type checker, but also add the extensions on Dictionary to take closure with 2 params (instead of a single 2-items tuple param), and the suggestion I added to deconstruct a tuple explicitly in my last comment, with all those 3 features we could have all the features that you need, + consistency + a Swift compiler and type checker that works faster. Sure that would give you less excuses to go take your coffee or switch to youtube to watch some cats videos while you're compiling, but I think that consistency, speed and still having the expressiveness with the solutions I suggest is a best way forward than trying to revert things and go backwards into a world of inconsistency and magic

@tmspzz
Copy link
Author

tmspzz commented May 25, 2017

@AliSoftware I think I will have other occasions for coffee and cat videos so I'm all up for speed while keeping expressivity :)

@AliSoftware
Copy link

AliSoftware commented May 25, 2017

Define this:

prefix operator *

prefix func *<A1,A2,R>(f: @escaping  (A1, A2) -> R) -> ((A1, A2)) -> R {
  return { (tuple: (A1, A2)) -> R in
    f(tuple.0, tuple.1)
  }
}

prefix func *<A1,A2,A3,R>(f: @escaping  (A1, A2, A3) -> R) -> ((A1, A2, A3)) -> R {
  return { (tuple: (A1, A2, A3)) -> R in
    f(tuple.0, tuple.1, tuple.2)
  }
}

// We could add some for tuples with 4 items, but I don't think people have a lot of real-world cases with tuples with more than 4 items (at that point, create a struct already)

Then even after SE-110 you can:

func foo(_ x: Int, _ y: Int) -> Int {
  return x + y
}

[(1,2),(3,4),(5,6)].map(*foo) // valid even after SE-110

let o1 = Observable<Int>.from(1,2,3)
let o2 = Observable<Int>.from(4,5,6)
Observables.zip(o1, o2, combine: *foo)

Note that, even before SE-110, in Swift 3 you were already not able to do this anymore:

let tuple = (1,2)
foo(tuple)
// error: passing 2 arguments to a callee as a single tuple value has been removed in Swift 3
// foo(tuple)
// ^  ~~~~~~~

So I'm not sure why people are so upset with SE-110 being applied to closures while the exact same rule was already applied to functions but nobody rioted back then ^^

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