2

I have this piece of code in Python :

def f(x, y):
    # do something...
    return f

I'm trying to write this in Swift but can't figure out if it's possible or not. The return type would get infinitely long.

Here's a part of the game I'm trying to recreate written in Python. It's a dice game with multiple commentary functions that get invoked on each round. After every round finishes, the commentary function could return itself but with some changes as well (such as changing variables in the enclosing scope).:

def say_scores(score0, score1):
    """A commentary function that announces the score for each player."""
    print("Player 0 now has", score0, "and Player 1 now has", score1)
    return say_scores

def announce_lead_changes(previous_leader=None):
    """Return a commentary function that announces lead changes."""

    def say(score0, score1):
        if score0 > score1:
            leader = 0
        elif score1 > score0:
            leader = 1
        else:
            leader = None
        if leader != None and leader != previous_leader:
            print('Player', leader, 'takes the lead by', abs(score0 - score1))
        return announce_lead_changes(leader)
    return say

def both(f, g):
    """Return a commentary function that says what f says, then what g says."""
    def say(score0, score1):
        return both(f(score0, score1), g(score0, score1))
    return say


def announce_highest(who, previous_high=0, previous_score=0):
    """Return a commentary function that announces when WHO's score
    increases by more than ever before in the game.
    assert who == 0 or who == 1, 'The who argument should indicate a player.'"""
    # BEGIN PROBLEM 7
    "*** YOUR CODE HERE ***"
    def say(score0,score1):
        scores = [score0,score1]
        score_diff = scores[who]-previous_score
        if score_diff > previous_high:
            print(score_diff,"point(s)! That's the biggest gain yet for Player",who)
            return announce_highest(who,score_diff,scores[who])
        return announce_highest(who,previous_high,scores[who])
    return say
    # END PROBLEM 7

The play function that repeats until some player reaches some score:

def play(strategy0, strategy1, score0=0, score1=0, dice=six_sided,
         goal=GOAL_SCORE, say=silence):
    """Simulate a game and return the final scores of both players, with Player
    0's score first, and Player 1's score second.

    A strategy is a function that takes two total scores as arguments (the
    current player's score, and the opponent's score), and returns a number of
    dice that the current player will roll this turn.

    strategy0:  The strategy function for Player 0, who plays first.
    strategy1:  The strategy function for Player 1, who plays second.
    score0:     Starting score for Player 0
    score1:     Starting score for Player 1
    dice:       A function of zero arguments that simulates a dice roll.
    goal:       The game ends and someone wins when this score is reached.
    say:        The commentary function to call at the end of the first turn.
    """
    player = 0  # Which player is about to take a turn, 0 (first) or 1 (second)
    # BEGIN PROBLEM 5
    "*** YOUR CODE HERE ***"
    scores = [score0,score1]
    strategies = [strategy0,strategy1]
    while score0 < goal and score1 < goal:
        scores[player] += take_turn(strategies[player](scores[player], scores[other(player)]),
        scores[other(player)], dice)

        swap = is_swap(scores[player], scores[other(player)])
        player = other(player)
        if swap:
            scores[0],scores[1] = scores[1], scores[0]
        score0,score1 = scores[0],scores[1]
    # END PROBLEM 5
    # BEGIN PROBLEM 6
        "*** YOUR CODE HERE ***"
        say = say(score0,score1)
    # END PROBLEM 6
    return score0, score1
jscs
  • 62,161
  • 12
  • 145
  • 186
  • Essentially, I have a bunch of functions like this that each does something before returning itself. In some other function, it takes in a function like this as the parameter. This way, I don't have to use switch/if statements to find out which function I want to use but could just pass it in and execute it and it could repeat over and over again. – Pirlo Lochisomo Feb 01 '19 at 12:48
  • 2
    I am fairly sure that a Swift function cannot return itself (unless you cast it to `Any` ☹️ : `func foo() -> Any { return foo as Any }`). – A more detailed description of what you are actually trying to achieve might be helpful. – Martin R Feb 01 '19 at 12:50
  • what is use of your return function `f`? – SPatel Feb 01 '19 at 12:54
  • 1
    Don't have time to write an answer this morning, but although you can't do exactly what you're asking for, depending on your use case you may be able to get essentially what you need by implementing an `IteratorProtocol`. From your Python code above, it kind of looks like you've essentially implemented `Iterator` there using a closure, and if that's indeed what you're doing, `Iterator` is the idiomatic way to do that in Swift. – Charles Srstka Feb 01 '19 at 12:55
  • Hey guys, sorry for not being able to explain this really well. Essentially, I'm building a dice game that repeats forever until some player reaches some score. Each round, some commentary function is called and it returns itself or some other commentary function so on the next loop it's updated and can be called again. Here's a link to the description of the game: https://inst.eecs.berkeley.edu/~cs61a/su18/proj/hog/#problem-6-2-pt – Pirlo Lochisomo Feb 01 '19 at 12:59
  • 1
    @PirloLochisomo Given that, you should definitely look into `IteratorProtocol`. You just keep calling `next()` on it, and each time it updates its internal state and gives you the current thing. You can even base a `Sequence` on it if you want. Quite nifty.\ – Charles Srstka Feb 01 '19 at 13:00
  • Somewhat relevant: https://stackoverflow.com/questions/26051683/ycombinator-not-working-in-swift – user28434'mstep Feb 01 '19 at 13:02
  • I am not a Python programmer – is this idiomatic Python, or a theoretical exercise? – Martin R Feb 01 '19 at 13:20
  • @MartinR I'm not super familiar with Python so I searched and found this: https://stackoverflow.com/questions/23753569/function-returning-reference-to-itself-python – Pirlo Lochisomo Feb 01 '19 at 13:23
  • 1
    These are highly relevant: [Recursive calls and trampolines in Swift](https://www.uraimo.com/2016/05/05/recursive-tail-calls-and-trampolines-in-swift/) [Translating recursive algorithms to iterative (in Python)](http://blog.moertel.com/posts/2013-05-11-recursive-to-iterative.html) – jscs Feb 01 '19 at 18:04

3 Answers3

8

Let's try to write such a thing.

func f() {
    return f
}

Now the compiler complains because f is not declared to return anything when it does return something.

Okay, let's try to add a return value type i.e. A closure that accepts no parameters and return nothing.

func f() -> (() -> ()) {
    return f
}

Now the compiler complains that f is () -> (() -> ()), and so cannot be converted to () -> ().

We should edit the declaration to return a () -> (() -> ()), right?

func f() -> (() -> (() -> ())) {
    return f
}

Now f becomes a () -> (() -> (() -> ())), which cannot be converted to a () -> (() -> ())!

See the pattern now? This will continue forever.

Therefore, you can only do this in a type-unsafe way, returning Any:

func f() -> Any { return f }

Usage:

func f() -> Any {
  print("Hello")
  return f
}
(f() as! (() -> Any))()

The reason why this is possible in python is exactly because Python is weakly typed and you don't need to specify the return type.

Note that I do not encourage you to write this kind of code in Swift. When you code in Swift, try to solve the problem with a Swift mindset. In other words, you should think of another way of solving the problem that does not involve a function like this.

Sweeper
  • 145,870
  • 17
  • 129
  • 225
  • Thanks. Would writing something like this in Swift be counted as bad convention/bad habit? Should I look for another way to implement this feature? – Pirlo Lochisomo Feb 01 '19 at 13:01
  • 3
    @PirloLochisomo When you code in Swift, you just shouldn't be thinking about "How would I solve this problem in Python?" Instead, try to solve the problem without using a function that returns itself. – Sweeper Feb 01 '19 at 13:02
2

Not exactly what you want perhaps but you can do something similar with a closure

typealias Closure = (Int) -> Int

func doStuff(action: @escaping Closure, value: Int) -> Closure {
    let x = action(value)
    //do something
    return action
}
Joakim Danielson
  • 29,280
  • 4
  • 14
  • 35
2

Well, actually you can do something like that in Swift, only you will have to separate the linear part of code from the recursive, and wrap recursive code in the struct:

// Recursive code goes here:
struct Rec<T> {
    let call: (T) -> Rec<T> // when code `from outside` calls it, it will execute linear part and return recursive
    init(closure: @escaping (T) -> Void) { // create new loop with linear `closure`
        self.call = {
            closure($0) // execute linear code
            return Rec(closure: closure) // return recursive wrapper
        }
    }

    subscript(input: T) -> Rec<T> { // this exist just to simulate `f(x)` calls, using square brackets notation 
        return self.call(input)
    }
}

// Linear code goes here
let sayScores = Rec { (score0: Int, score1: Int) in
    print("Player 0 now has", score0, "and Player 1 now has", score1)
}

Usage:

let temp = sayScores.call((1, 2)) // will print: Player 0 now has 1 and Player 1 now has 2

temp[(0, 0)][(10, 42)] // temp is `Rec<(Int, Int)>`
// will print:
// Player 0 now has 0 and Player 1 now has 0
// Player 0 now has 10 and Player 1 now has 42

So you may make it work, but I don't know whether you should use it in Swift.

jscs
  • 62,161
  • 12
  • 145
  • 186
user28434'mstep
  • 5,401
  • 1
  • 16
  • 31
  • Generally I think the closure here would be called a "continuation". – jscs Feb 01 '19 at 18:06
  • I wonder if you could (ab)use the new "dynamically callable" for this, instead of the explicit `call` property... – jscs Feb 02 '19 at 02:01
  • 1
    @JoshCaswell, only if `T == String`. Because you will have to use argument as `dynamic` name, and only `String`s will work. Like if had `Rec` "function" `greet` printing out `"Hello, \(stringArgument)"`, we could call it like `greet.world` to get "Hello, world". – user28434'mstep Feb 02 '19 at 16:11