[swift-evolution] TreeLiteralConvertible

Milos Rankovic milos at milos-and-slavica.net
Fri Apr 15 03:29:40 CDT 2016


> On 15 Apr 2016, at 03:22, John McCall <rjmccall at apple.com> wrote:
> 
> The heterogeneity that I'm referring to is the mix of sub-trees and leaves at a single level.
… which is why I was making the point that that part of the heterogeneity problem is solved.

> Your JSON literal example is already pretty well modeled by simply making a JSONValue type that conforms to all the literal protocols.  It is completely unclear why you would even want to model this with some generic Tree structure. 
Because JSON has the structure of a tree. A dictionary of the type `[String:AnyObject]` does not express that structure even if at run-time it turns out that some of those any-objects are themselves dictionaries. `Tree<String, JSONValue>`, in contrast, precisely expresses the structure of JSON objects. `[String:JSONValue]` is flat so it is not worth talking about, whilst some dedicated `JSON` enum is just that, a dedicated type that is the symptom of a language incapable of representing things like `[1, [2]]` as such. 

You do have a good point about conforming JSONValue type to the literal-convertible protocols. That is important here because it reminds us that the question of converting Int and String values to JSONValue type in my example is a completely separate issue from my proposal. This in turn shows that the two tree literal-convertable protocols I’d love to see added to the Standard Library would be sufficient in all cases. For instance (assuming the code from my original post is already in the context):

enum JSONValue {
	case Integer(Int)
	case Text(String)
	case URL(NSURL)

	init(_ string: String) {
		if let url = NSURL(string: string) {
			self = .URL(url)
		} else {
			self = .Text(string)
		}
	}
}

extension JSONValue : UnicodeScalarLiteralConvertible {
	init(unicodeScalarLiteral value: String) { self.init(value) }
}
extension JSONValue : ExtendedGraphemeClusterLiteralConvertible {
	init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
extension JSONValue : StringLiteralConvertible {
	init(stringLiteral value: String){ self.init(value) }
}

extension JSONValue : IntegerLiteralConvertible {
	init(integerLiteral value: Int) { self = .Integer(value) }
}

let city: JSONValue = .Text("New York")

let johnny: DictionaryTree<String, JSONValue> =
[
	"name": ◊"Johnny Appleseed",
	"age": ◊25,
	"github": ◊"http://github.com/apple/swift-evolution",
	"address": [
		"number": ◊21,
		"street": ◊"2nd Street",
		"city": ◊city
	]
]

This is what we can do already (try it in the playground - even the url is read as a url). However, if we were also given the following protocol:

protocol DictionaryTreeLiteralConvertible {
	associatedtype Key
	associatedtype LeafValue
	init(literal: Self.LeafValue...)
	init(literal: (Key, Self)...)
}

… then the lifting of the JSONValue-s into the recursive world of trees would be done by the `init(literal: Self.LeafValue…)` and we would no longer need the lifting operator ◊.

Finally, it is worth mentioning that the two proposed protocols, TreeLiteralConvertible and DictionaryTreeLiteralConvertible, should be simple to implement, would not affect existing code, and would allow us to work with all kinds of trees and nested associative arrays with ease, safety and elegance we are growing accustomed to when programming in Swift.

milos


> On 15 Apr 2016, at 03:22, John McCall <rjmccall at apple.com> wrote:
> 
>> On Apr 14, 2016, at 2:56 PM, Milos Rankovic <milos at milos-and-slavica.net <mailto:milos at milos-and-slavica.net>> wrote:
>> Hi John and Brent, 
>> 
>>> On 14 Apr 2016, at 22:22, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>> 
>>> multiple-conformance idea doesn't work
>> 
>> 
>> The idea is not multiple-conformance (or overloading), but multiple (two) initialisers required by the literal-convertible protocols:
>> 
>> protocol TreeLiteralConvertible {
>> 	associatedtype LeafValue
>> 	init(literal: Self.LeafValue...)
>> 	init(literal: Self...)
>> }
>> 
>> … and:
>> 
>> protocol DictionaryTreeLiteralConvertible {
>> 	associatedtype Key
>> 	associatedtype LeafValue
>> 	init(literal: Self.LeafValue...)
>> 	init(literal: (Key, Self)...)
>> }
>> 
>>> Note that all of your examples rely not just on recursion but on heterogeneous recursion
>> 
>> The crux of the matter is not heterogeneity in general, but of the leaf value in particular. This is what Brent is addressing. All my examples, save one, had a uniform leaf value type (even the Tree<SKAction> example).
> 
> The heterogeneity that I'm referring to is the mix of sub-trees and leaves at a single level.
> 
>> The one exception is my second JSON example. There I did not post the lift operator overload as you can probably imagine it. Minimally:
> 
> It's pretty implausible that we'd ever add a "tree literal" concept that serves exactly your use case, so I'm looking for ways to capture it that fit within the existing language framework, or at least take advantage of a more general addition to the language.
> 
> Your JSON literal example is already pretty well modeled by simply making a JSONValue type that conforms to all the literal protocols.  It is completely unclear why you would even want to model this with some generic Tree structure.  Note that neither your tree-literal-protocol proposal nor Brent's lifting-protocol proposal is actually adequate for embedding non-literal Int/Double/Bool values in the structure because they both only allow a single "leaf" type.
> 
> Your other examples could be modeled with either a lifting protocol or a conditional conformance.  I was just noting that the conditional conformance would be adequate if you were willing to manually lift non-literal values, and conditional conformances are a feature that's already basically planned, as opposed to a new research project.
> 
> John.
> 
> 
> 
> 
> 
> 
>> 
>> enum JSONValue {
>> 	case Text(String)
>> 	case Integer(Int)
>> }
>> 
>> prefix func ◊ <Key> (leaf: String) -> DictionaryTree<Key, JSONValue> {
>> 	return .Leaf(.Text(leaf))
>> }
>> 
>> prefix func ◊ <Key> (leaf: Int) -> DictionaryTree<Key, JSONValue> {
>> 	return .Leaf(.Integer(leaf))
>> }
>> 
>> 
>> let johnny: DictionaryTree<String, JSONValue> =
>> [
>> 	"name": ◊"Johnny Appleseed",
>> 	"age": ◊25,
>> 	"address": [
>> 		"house_number": ◊21,
>> 		"street": ◊"2nd Street",
>> 		"city": ◊"New York"
>> 	]
>> ]
>> 
>> Notice in particular how much contextual information you are getting from the expected return type. Still though, as Brent, points out, this won’t work with the two literal-convertable protocols. Nevertheless, I’d be very happy if they could be added as a first step since I suspect that would be the easiest option and one that would still allow for all my examples so far to work without the lift operator; all except this `JSONValue` example.
>> 
>> milos

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160415/0564592f/attachment.html>


More information about the swift-evolution mailing list