Golo et Swift, étude comparée
Introduction
Le 2 juin 2014, lors de la keynote de la WWDC d’Apple, un nouveau langage pour créer des applications iOS, OSX, appelé Swift a été présenté. Il rappelle beaucoup Groovy (voir le comparatif Groovy versus Swift: http://glaforge.appspot.com/article/apple-s-swift-programming-language-inspired-by-groovy par Guillaume Laforge).
Golo (non typé) n’a certainement pas inspiré Swift (typé), mais je retrouve quelques aspects fonctionnels similaire. Voyons de quelle manière à travers quelques exemples de codes comparés.
Pour ceux qui ne connaissent pas Golo, c’est un langage dynamique pour la JVM. Plus d’informations ici: http://golo-lang.org/.
Fonctions
Une fonction qui ne retourne rien (void)
Swift:
func printHello(message: String) {
println("hello \(message)")
}
printHello("world")
Golo:
function printHello = |message| {
println("hello " + message)
}
printHello("world")
ou:
function printHello = |message| -> println("hello " + message)
Une fonction qui retourne quelque chose
Swift:
func hello(message: String) -> String {
return "hello \(message)"
}
hello("world")
Golo:
function hello = |message| {
return "hello " + message
}
ou:
function hello = |message| -> "hello " + message
Nested functions (ou Closures ?)
Les “nested functions” sont des définitions de fonctions à l’intérieur de fonctions, ce qui signifie qu’elles ne sont accessibles qu’à l’intérieur de la fonction qui les contient.
Exemple simple
Swift:
func salut(message: String) -> String {
func nestedHello(message: String) -> String {
return "Hello \(message)"
}
return nestedHello(message)
}
salut("World !!!")
Golo:
function salut = |message| {
let nestedHello = |message| -> "hello " + message
return nestedHello(message)
}
salut("World !!!")
Mais une fonction peut aussi retourner une fonction.
Une fonction qui retourne une fonction
Swift:
func hola(startMessage: String) -> String -> String {
func nestedHello(endMessage: String) -> String {
return "\(startMessage) \(endMessage)"
}
return nestedHello
}
Et cette fois ci l’appel de notre méthode hola
se fera de la façon suivante:
hola("Hello")("World !!!")
Golo:
function hola = |startMessage| {
let nestedHello = |endMessage| -> startMessage + " " + endMessage
return nestedHello
}
ou
function hola = |startMessage| ->
|endMessage| -> startMessage + " " + endMessage
Qui sera aussi appelé de cette manière:
hola("Hello")("World !!!")
Une fonction comme paramètre
Swift:
func morgen(startMessage:String, endMessage: String -> String) -> String {
let param = "World"
return "\(startMessage) \(endMessage(param))"
}
func message(msg: String) -> String {
return "\(msg) !!!"
}
morgen("Hello", message)
Golo:
function morgen = |startMessage, endMessage| {
return startMessage + " " + endMessage("World")
}
puis:
let message = |msg| -> msg + " !!!"
morgen("Hello", message)
“Closures Expressions”
Selon Apple, une “Closure Expression” est un moyen “synthétique” pour écrire/définir une closure. C’est plus simplement ce qui va nous permettre de “simplifier” notre exemple précédent en définissant “inline” le paramètre endMessage
sans passer par la fonction intermédiaire message
de la façon suivante:
Swift:
morgen("Hello", { (msg: String) -> String in
return "\(msg) !!!"
})
Golo:
morgen("Hello", |msg| -> msg + " !!!")
Lire les “Closures en Golo” : http://golo-lang.org/documentation/next/# _closures.
Un peu plus loin, avec les extensions
Les “extensions” dans Swift permettent d’ajouter des fonctionnalités (méthodes) à des types (Classes, Structures …), ce qui permet “d’augmenter” le langage lui même en alliant les “extensions” aux “closures expressions” et aux “generics”.
En Golo on parle des “augmentations” (le principe est le même).
Méthode first()
Il n’y a pas de méthode first
sur le type Array
. Qu’à cela ne tienne, il suffit d’écrire ceci:
Swift:
extension Array{
func first() -> (T) {
return self[0]
}
}
var villes = ["Lyon", "Valence", "Chambéry"]
villes.first()
Et villes.first()
retournera Lyon
, et c’est plus “joli” (question de point de vue) que villes[0].
Golo:
augment java.util.List {
function first = |self| -> self: get(0)
}
et on l’utilise comme ceci:
var villes = list["Lyon", "Valence", "Chambéry"]
villes: first() # retourne "Lyon"
Méthode each()
En Swift pour parcourir un tableau, il faut écrire ceci:
for ville: String in villes {
println(ville)
}
Je souhaite avoir une méthode each
pour le type Array
, je vais donc modifier mon extension Array
:
extension Array{
func each(each: (T) -> ()){
for object: T in self {
each(object)
}
}
func first() -> (T) {
return self[0]
}
}
Et maintenant, je peux écrire ceci:
villes.each({ (item: String) in
println(item)
})
L’inférence de Swift me permet même d’écrire ceci:
villes.each({ item in
println(item)
})
Golo:
En Golo cette augmentation existe déjà:
villes: each(|ville| -> println(ville))
Les classes
La définition d’une classe en Swift reste somme toute très classique:
class Human {
var _firstName: String
var _lastName: String
init(firstName: String, lastName: String) {
_firstName = firstName
_lastName = lastName
}
init() {
_firstName = "John"
_lastName = "Doe"
}
func hello() -> String {
return "Hello, i'm \(_firstName) \(_lastName)"
}
}
let John = Human()
John.hello()
let Bob = Human(firstName:"Bob", lastName:"Morane")
Bob.hello()
En Golo:, il n’y a pas de classe ni d’interface, mais il existe les DynamicObjects qui couplés à une fonction comme constructeur peuvent “faire office de classe”:
function Human = ->
DynamicObject()
: firstName("John")
: lastName("Doe")
: define("hello", |self| {
return "Hello, i'm " + self: firstName() + " " + self: lastName()
})
let John = Human()
John: hello() # retourne "Hello, i'm John Doe"
let Bob = Human(): firstName("Bob"): lastName("Morane")
Bob: hello() # retourne "Hello, i'm Bob Morane"
Remarque: En Swift comme en Golo il n’y a pas le concept de variable privée (sauf un cas particulier en Golo), il faut donc encapsuler les Classes dans des fonctions pour Swift, et les DynamicObjects dans des fonctions pour Golo.
Propriétés
Avec Swift, comme en Objective-C, le concept de propriétés existe et notamment celui de Computed Properties (pour remplacer les getters et les setters):
var firstName: String {
get {return _firstName}
set(value) {
_firstName = value
}
}
var lastName: String {
get {return _lastName}
set(value) {
_lastName = value
}
}
En Golo, de fait, les membres d’un DynamicObject qui ne sont pas des méthodes sont des propriétés (des getters et setters):
let Bob = DynamicObject(): name("Bob")
Bob: name() # retourne "Bob"
Bob: name("Bob Morane") # change la valeur de name
Héritage
En ce qui concerne l’héritage, en Swift il suffit de déclarer juste après le nom de la classe le type (la classe) dont elle hérite:
class SuperHero: Human {
var power = "walking"
var nickName = "Kick-Ass"
override func hello() -> String {
return super.hello() + ", my Hero name is \(nickName) and i'm \(power)"
}
}
let Clark = SuperHero(firstName: "Clark", lastName: "Kent")
Clark.nickName = "SuperMan"
Clark.power = "flying"
Clark.hello() // retourne "Hello, i'm Clark Kent, my Hero name is SuperMan and i'm flying"
En Golo:, nous n’avons pas de classe mais nous pouvons “faire des mixins” de DynamicObject() :
function Human = -> DynamicObject()
: firstName("John")
: lastName("Doe")
: define("hello", |self| {
return "Hello, i'm " + self: firstName() + " " + self: firstName()
})
function SuperHero = -> DynamicObject()
: power("walking")
: nickName("Kick-Ass")
: define("hello", |self| {
return "Hello, i'm " + self: firstName() + " " + self: firstName() +
", my Hero name is " + self: nickName() + " and i'm " + self: power()
})
let Clark = Human(): mixin(SuperHero())
: firstName("Clark"): lastName("Kent")
: nickName("SuperMan"): power("flying")
Clark: hello() # retourne "Hello, i'm Clark Clark, my Hero name is SuperMan and i'm flying"
Les Structures
En plus des classes, Swift propose les structures. La déclaration se fait de la même manière que les classes, une structure peut implémenter un protocole (interface), être étendue par une extension, par contre elle ne peut hériter. Mais le plus important c’est que les classes sont des types par référence et les structures des types par valeur:
let Donald = Duck(firstName: "Donald", lastName: "Duck")
var Daffy = Donald
Daffy.firstName = "Daffy"
Donald.hello() // retourne Hello, i'm Donald Duck
Daffy.hello() // retourne Hello, i'm Daffy Duck
Donald.hello()
retournera toujours Hello, i'm Donald Duck
. Daffy
est une copie de Donald
et donc devient indépendant de Donald
. Vous pouvez notez que les structures disposent d’un constructeur par défaut que nous ne sommes pas obligés de définir.
En Golo:, il existe aussi des structures qui ne contiennent pas de méthode dans leur définition, mais qui peuvent être augmentées. Elles disposent aussi d’un constructeur par défaut. Mais attention, les structures Golo, à la différence de Swift, sont des types par référence, il faudra donc utiliser la méthode copy()
des structures Golo pour faire une copie “par valeur”:
struct Duck = { firstName, lastName }
augment Duck {
function hello = |self| -> "Hello, i'm " + self: firstName() + " " + self: lastName()
}
let Donald = Duck("Donald", "Duck")
let Daffy = Donald: copy()
Daffy: firstName("Daffy")
println(Donald: hello()) # retourne "Hello, i'm Donald Duck"
println(Daffy: hello()) # retourne "Hello, i'm Daffy Duck"
Remarque: on peut avoir des membres privés dans les structures en préfixant les noms des membres par _
, ils deviendrons ainsi inaccessibles. Cf. http://golo-lang.org/documentation/next/# _private_members
Conclusion
Il y a quand même une distance entre les 2 langages qui n’ont pas la même vocation. Cependant le côté fonctionnel des 2 me permet de calquer ma logique de programmation dans les 2.
Tweet