I have this function supported by 2 enums. Hopefully the code is self documenting:
import Foundation
enum Theme: String {
case regular = "๐๐๐"
case cat = "๐พ๐ฑ๐บ"
}
enum Sentiment: Int {
case sad
case neutral
case happy
init(rating: Double) {
self = rating < 0.4 ? .sad : rating < 0.7 ? .neutral : .happy
}
}
/// Produce a themed emoji based on a Sentiment
/// - parameter sentiment: Sentiment to represent
/// - parameter theme: Theme for expressing the sentiment
func themedSentiment(_ sentiment: Sentiment, theme: Theme = .regular) -> Character {
// Choose Emoji in Theme
return Array(theme.rawValue)[sentiment.rawValue] // TODO: better way to get a char from string?
}
themedSentiment(.happy) // ๐
themedSentiment(.sad, theme: .cat) // ๐พ
themedSentiment(Sentiment(rating: 0.9), theme: .cat) // ๐บ
As these enums are only for use by the function, I find myself wishing I could define them within the function body, so as to have a 'fully contained function'.
While I actually can move the enum definitions into the function body, they are then not available to use as the functions parameter types. Would it be desirable (sometimes) for a functions parameter to make use of the functions private type. Or is that a naughty idea?
On a separate matter, I'm not crazy about the initialiser either:
Specifically I don't like having to reuse 'rating' for each test.
I could use a closure with a switch:
init(rating: Double) {
self = { () -> Sentiment in switch rating { case ..<0.4 : return .sad case ..<0.7 : return .neutral default : return .happy} }()
}
But that's verbose and has to explicitly return each value. I found myself wishing for something in-between a switch and the ternary operator, something like:
init(rating: Double) {
self = choose rating case ..<0.4 : .sad case ..<0.7 : .neutral default: .happy
}
Please feel free to tear into these if they are silly ideas
PS: Is there is FAQ on trying to format code in these posts. It seems to have a mind of its own!
The enumerations are part of the user interface for your function, so they should be defined external to your function. If you think the emojis should be private, then you need to split them from the Theme enumeration, put them into a private structure within your function, and using the function parameters to look up the character.
This definition of your Sentiment enum seems to do the trick:
enum Sentiment: Int {
case sad
case neutral
case happy
init(rating: Double) {
switch rating {
case ..<0.4 : self = .sad
case ..<0.7 : self = .neutral
default : self = .happy
}
}
}
You are going to have to make a decision at some point. Not sure what your problem is with "multiple assignment" statements, only one ever gets executed. You are going to have to quantize your Double into one of three ranges, and based on the particular range it falls in, assign it the enumeration code. If you make Sentiment RawRepresentable, you could assign the value based on the "index" of the range in which your value falls, but, to figure out the range is going to require some floating point manipulation and you're still going to need some decision making logic. All of which is going to make the whole thing a bear to maintain over time.
It's about being expressive; the code needs to make one assignment from a selection of possible values. That's what the code is actually doing, therefore that's the way I like to write it.
Consider this:
`
if result > limit {
let overflow = true
} else {
let overflow = false
}
`
This is of no use because it defines overflow out of scope of the mainline code.
So we have to declare first and set later. At least Swift allows this for constants and doesn't force mutability:
`
let overflow: Bool
if result > limit {
overflow = true
} else {
overflow = false
}
`
But we have lost the benefit of type inference, and now have used 5 lines. (also the assignment could happen far away from the declaration, and make the constant look like a variable to the unsuspecting).
So for me:
`
let overflow = result > limit ? true : false
`
Is a much cleaner solution, and so I think it would be even nicer to extended the idea to a set of values rather than limited to two.
But as I say I'm just thinking out loud. I'm interested in discussing it, rather than just being told, that's the way it is. So perhaps this is not the right forum. I'm still new to this site and figuring it out. Heck, I haven't even figured out how to format the code without jumping through hoops.
How is it inconsistent? If a class or struct uses a custom type in its public interface, that type must be public, too. Seems consistent to me.
If your function takes a Foo, you must be able to pass it a Foo. Then you must be able to create (or store, reference) a Foo. So your Foo must be accessible, either publicly or internally or whatever matches your scope.
Yes, but when Foo only makes sense to the function you don't necessarily want someone else trying to make use of it (because a subsequent change to your implementation could break their code).
If the enum cases (and not the raw values) were only externally visible as parameters to the function it would avoid this.
If Foo only makes sense to the function, then, it shouldn't be a parameter in the function's interface. It has to make sense to the context in which the function is executed.
Is there a construct in another language you are trying to emulate within Swift that somehow meets your aesthetic?
Switches as expressions have been discussed in-depth many-many times already, and is on the list of commonly rejected changes. Although I, too, often find that it would be useful to have switch-statements return a value, it is not likely to happen unless you bring considerable new data to the table. The first part of that pitch, should probably include an exhaustive list of past discussions, and clearly communicate why the arguments against previous proposals are no longer relevant.
On your idea for making function-private types accessible externally, I fail to see how that would even work. What are the semantics? How do you pass a value you cannot construct? Is it possible to construct them, but just in the function call parameter position? If so, can you wrap their constructions in inline expressions, such as themedSentiment(isSad ? .sad : .happy)? If so, why can't I extract the expression into a local variable? Why can't I create a helper function that returns my preferred sentiment given some state?
Well yeah, function is a first-class type, as-in, they can be anywhere other first-class type like class/struct/enum is expected; as variable, function argument, etc.
Note also that your declaration is of type
(Sentiment, Theme) -> Character
and name
themedSentiment(sentiment:theme:)
So if youโre nesting a declaration publicly, youโd need to add it to (Sentiment, Theme)->Character, not themeSentiment. Otherwise it wouldnโt be terribly useful.
The same thing applies if you have variable like let a: Int. You wouldnโt add nested type to a, youโd add it to Int.
The problem is that youโre trying to add nested type to a function. Function is whatโs called compound type, because it consists of multiple types patched together in a very specific way. Tuple type is also a compound type.
The main thing is that the compound type doesnโt have a name on its own, and you can end up creating, say (Int) -> Int multiple times, at different places. So the problem with extending compound type, as well as adding a nested type to it, is that itโs not clear who will be able to use the extension. The type itself? Anywhere the compound signature matches? What if there are conflicts in the seemingly unrelated places? There are many pitch about extending compound type (non-nominal type) in the past.