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.

blog comments powered by Disqus

Related posts