Skip to content

Instantly share code, notes, and snippets.

@mathonsunday
Created December 8, 2015 23:06
Show Gist options
  • Select an option

  • Save mathonsunday/304baf3df67ecaf97660 to your computer and use it in GitHub Desktop.

Select an option

Save mathonsunday/304baf3df67ecaf97660 to your computer and use it in GitHub Desktop.
//: Playground - noun: a place where people can play
import Cocoa
import Foundation
// WinterBuddies a social network to connect you with people who can help you survive the winter
class ProfileViewController : UIViewController {
let musicService = MusicService()
init(musicService: MusicService) {
self.musicService = musicService
}
override func viewDidLoad() {
super.viewDidLoad()
musicService.play() // start playlist on application
}
}
// ProfileViewControllerTests.swift
func testShouldPlayMusicWhenViewDidLoad() {
// setup mock
class MockMusicService : MusicService {
var musicPlayed = false
override func play() {
musicPlayed = true
}
}
var viewController = ProfileViewController(musicServicer: MockMusicService())
// invoke
viewController.viewDidLoad()
// verify
XCTAssertTrue(mockMusicService.playCalled)
}
class MockMusicService: MusicService {
class func basic() -> MusicService {
return builder().build()!
}
class func builder() -> MusicServiceBuilder {
return MusicServiceBuilder()
.withApplication("Spotify")
.withPlaylist("Chill Party")
}
}
extension MusicServiceBuilder {
func withApplication(application: String) -> MusicServiceBuilder {
self.application = application
return self
}
func withPlaylist(playlistName: String) -> MusicServiceBuilder {
self.playlistName = playlistName
return self
}
}
@mpurland
Copy link

I tend to agree that the Reader monad may provide less value than in Haskell or Scala because of the lack of syntax support in Swift. Haskell do and Scala for-comprehension notation is great for this use case. I think this would also depend on the use case your going to use it in Swift. I'm not an expert, but it's definitely interesting to think about.

If you want constructor injection of only a single type then it may make sense to use simple constructor injection. I think the power of the Reader monad is about the idea that it represents. Given an environment E it will output the modified environment A. It's essentially a function E -> A. If we create our MusicService reader that takes in the environment Config and returns a MusicService.

let musicServiceReader = Reader<Config, MusicService> 

This would allow the Config to be injected anywhere as a dependency while allowing computations on the monad to be performed without extracting the Config state from the Reader. As far as I understand it this is possible by implementing fmap, apply, and flatMap (bind >>-) for the monad in question, the Reader monad. The series of computations are performed without necessarily unwrapping the environment as context from the Reader.

Then if we change the definition slightly:

let configReader = Reader<Void, Config>
let musicReader = configReader.fmap { $0.musicService } // musicReader is of type Reader<Void, MusicService>

This might allow the configReader to be passed in anywhere it's needed but still allow the extraction of the needed environment while applying operations to it like a monad. This would allow the setup and logic for the particular Reader or Config in this case to be encapsulated and separate from the rest of the control flow.

The example might then be:

protocol MusicService {
    func play(artist: Artist)
}

class AwesomeMusicService: MusicService {
    func play(artist: Artist) {
        print("artist: \(artist) brought to you buy Radio AMS.")
    }
}

class MockMusicService: MusicService {
    var lastArtistPlayed: Artist? = nil

    init() {
    }

    func play(artist: Artist) {
        lastArtistPlayed = artist
    }
}

struct Artist {
    let name: String
}

protocol Config {
    var musicService: MusicService { get }
    var favoriteArtist: Artist { get }
}

struct MyConfig: Config {
    var musicService: MusicService
    var favoriteArtist: Artist

    init() {
        musicService = AwesomeMusicService()
        favoriteArtist = Artist(name: "Ferry Corsten")
    }
}

struct MockConfig: Config {
    var musicService: MusicService
    var favoriteArtist: Artist

    init() {
        musicService = MockMusicService()
        favoriteArtist = Artist(name: "Mock")
    }
}

func musicPlayFavoriteArtist(config: Config) {
    config.musicService.play(config.favoriteArtist)
}

class ProfileViewController : UIViewController {
    let configReader: Reader<Void, Config>
    init(configReader: Reader<Void, Config>) {
        self.configReader = configReader
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        musicPlayFavoriteArtist <^> configReader >>- runReader
    }
}

/// Some standalone examples
let mockConfigReader = Reader<Void, Config>.pure(MockConfig())
musicPlayFavoriteArtist <^> mockConfigReader >>- runReader
// Played "Mock"

let myConfigReader = Reader<Void, Config>.pure(MockConfig())
musicPlayFavoriteArtist <^> myConfigReader >>- runReader
// Played "Ferry Corsten"

@mathonsunday Let me know if this makes sense or needs clarification.

@mathonsunday
Copy link
Author

Not clear to me why this is desirable

The series of computations are performed without necessarily unwrapping the environment as context from the Reader.

Does it necessarily lead to

This would allow the setup and logic for the particular Reader or Config in this case to be encapsulated and separate from the rest of the control flow.

So, for the test I'd do:

// ProfileViewControllerTests.swift
func testShouldPlayMusicWhenViewDidLoad() {

    let mockConfigReader = Reader<Void, Config>.pure(MockConfig())
    var viewController = ProfileViewController(configReader)

    // invoke
    viewController.viewDidLoad()

    // verify
    XCTAssertTrue(mockConfigReader.musicPlayFavoriteArtist) // our approach might not jibe well with this 
}

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