vendredi 9 juillet 2010

test

 

#light

namespace ViLogo

 
module World =

   
open System

   
type World =
        {
            pos : (float * float)
            angle : float
            penDown : bool
        }
   
with 
       
static member init = {pos = (0.0, 0.0); angle = Math.PI/2.0; penDown = true}

   
type Command = 
        |Forward
of float
        |Turn
of float
        |Zero
        |Angle
of float
        |Pos
of (float * float)
        |Erase
        |PenDown
of bool

   
type CommandResult = int

   
let executeCommand command world = 
       
match command with
        |Forward d
->   let x = fst world.pos + d * Math.Cos(world.angle)
                       
let y = snd world.pos + d * Math.Sin(world.angle)
                        None, {world
with pos = (x,y)}
        |Turn a
->  None, {world with angle = world.angle + a*Math.PI/180.0}
        |Zero
-> None, World.init
        |Angle a
->  None, {world with angle = a*Math.PI/180.0}
        |Pos (x, y)
-> None, {world with pos = (x,y)}
        |Erase
-> None, world
        |PenDown b
-> None, {world with penDown = b}

        

mardi 16 décembre 2008

Programmation Reactive Fonctionnelle en F# (1)

Bonjour,

Ce message est le premier d' une série que j' espère longue et enrichissante pour le plus grand nombre d' entre-vous.

Suite à la lecture de l' ouvrage de Paul Hudak: "The Haskell School of Expression", j' ai découvert le monde un peu particulier de la Programmation Fonctionnelle Réactive.

Après avoir lu une bonne partie des articles publiés sur le sujet - et disponibles assez facilement, càd sur internet, j' ai eu l' idée de mettre en pratique ces recherches dans le cadre d' une implémentation en F#.

Et ce avec les objectifs suivants:

  • Mieux comprendre ce domaine très particulier.
  • Me permettre d' évaluer F# dans un cadre vraiment fonctionnel.

Ceci étant dit, je ne vais pas ici faire une présentation exhaustive de ce qu' est la Programmation Fonctionnelle Réactive. D' autres l' ont fait - et certainement mieux que moi - Cf les livres et articles ci-dessus pour une bonne introduction.

Néanmoins, rien n' empêche un petit rafraîchissement de mémoire.

Un programme fonctionnel réactif est un programme qui est écrit dans un langage fonctionnel et qui produit des résultats qui dépendent de façon continue du temps d' une part et d' événements discrets d' autre part.

Je commencerais d' abord par examiner comment il est possible de représenter en F# une quantité variant de façon continue dans le temps. Dans le monde de la FRP, une telle quantité est souvent appelée un Behavior.

La solution le plus évidente est de représenter cette quantité par une fonction qui prend un temps t en argument et retourne la valeur de cette quantité au temps t. Donc si on considère que le temps est représenté par un float, on obtient le type générique Behavior.

#light

type Time = float

type 'a Behavior = Time -> 'a
En fait, on utilise de préférence la formulation suivante. La raison de cela apparaîtra un peu plus tard dans ce blog.

type 'a Behavior = Beh of (Time -> 'a)
A l' aide de cette déclaration, on peut déjà définir un certain nombre de Behavior simples

Type de Behavior Implémentation
Une quantité constante égale à 1

let oneB = Beh (fun _ -> 1.0)

Une quantité constante égale à "hello world!"

let helloB = Beh (fun _ -> "Hello World!")

Un Behavior qui représente le temps

let timeB = Beh (fun t -> t)

Un Behavior qui représente le temps multiplié par 3

let time3B = Beh (fun t -> t * 3.0)

Une fonction sinus du temps let sinB = Beh (fun t -> System.Math.Sin t)
Une fonction sinus du triple du temps let sin3B = Beh (fun t -> System.Math.Sin (t * 3.0))

A stade il est déjà possible de se simplifier la vie en définissant certains combinateurs utiles.

constB: permet de transformer une constante en un Behavior

//val constB : 'a -> 'a Behavior

let constB v = let bf t = v
               in
               Beh bf



let oneB = constB 1
let helloB = constB "Hello World!"

En ce qui concerne les 3 derniers Behaviors la situation est un petit peu plus complexe. Par exemple, time3B ressemble fortement à timeB et il est possible d' exprimer time3B en fonction de timeB:

let time3B = let (Beh time) = timeB
             let bf = fun t -> 3.0 * (time t)
             in Beh bf

time3B peut encore être transformé en déplaçant timeB et la fonction ((*) 3.0) en dehors du corps de l' expression de time3B:

let time3B = let liftB f b = let (Beh bf) = b
                             let lbf = fun t -> f (bft)
                             in Beh lbf
             in liftB ((*) 3.0) timeB
liftB étant indépendant de tout le reste, on peut le définir comme un combinateur à part entière:

// val liftB : ('a -> 'b) -> 'a Behavior -> 'b Behavior

let liftB f b = let (Beh bf) = b
                let lbf = fun t -> f (bf t)
                in Beh lbf
Dés lors, il est maintenant très simple de définir time3B mais également sinB et sin3B à l'aide de liftB:

let time3B = liftB ((*) 3.0) timeB
let sinB = liftB System.Math.Sin timeB
let sin3B = liftB System.Math.Sin (liftB ((*) 3.0) timeB)
On voit que l' utilisation de liftB permet de combiner fonctions et Behaviors afin de produire de nouveaux Behaviors. Par contre l' écriture est encore assez lourde.

Il est possible de la simplifier en regardant de plus près la signature de liftB. Il s' agit d' une fonction qui prend comme argument une fonction transformant une valeur de type 'a en une valeur de type 'b et retourne une fonction qui transforme un 'a Behavior en un 'b Behavior. liftB est donc une fonction qui monte ou transforme (lift en anglais - d' où le nom) une fonction définie sur des types quelconques ('a, 'b) vers une fonction définie sur des Behaviors.

Dés lors, on peut définir:

let sinF = liftB System.Math.Sin
let sinB = sinF timeB

let tripleF = liftB ((*) 3.0)

let sin3B = sinF (tripleF timeB)
De la même façon, des combinateurs permettant de lifter des fonctions a plusieurs arguments peuvent être définis:

//val lift2B : ('a -> 'b -> 'c) -> 'a Behavior -> 'b Behavior -> 'c Behavior

let lift2B f b1 b2 = let (Beh bf1) = b1
                     let (Beh bf2) = b2
                     let nbf t = f (bf1 t) (bf2 t)
                     in Beh nbf

//val lift3B : ('a -> 'b -> 'c -> 'd) -> 'a Behavior -> 'b Behavior -> 'c Behavior -> 'd Behavior

let lift3B f b1 b2 b3 = let (Beh bf1) = b1
                        let (Beh bf2) = b2
                        let (Beh bf3) = b3
                        let nbf t = f (bf1 t) (bf2 t) (bf3 t)
                        in Beh nbf
Quelques exemples de fonctions liftées.

// val ( .* ) : (int Behavior -> int Behavior -> int Behavior)

let (.*) = lift2B (*)

// val ( ./ ) : (int Behavior -> int Behavior -> int Behavior)

let (./) = lift2B (/)

// val mapB : ('a -> 'b) Behavior -> 'a list Behavior -> 'b list Behavior

let mapB f b = (lift2B List.map) f b
Remarque: mapB est définie comme une fonction à deux arguments car si tel n' était pas le cas, le compilateur F# reporterait la presque omniprésente erreur de "Value restriction".

error FS0030: Value restriction. Type inference has inferred the signature
val mapB : (('_a -> '_b) Behavior -> '_a list Behavior -> '_b list Behavior)
Either define 'mapB' as a syntactic function, or add a type constraint to instantiate the type parameters.
Et pour terminer ce premier message, voici quelques fonctions permettant d' exécuter un Behavior.

// val runOne : 'a Behavior -> Time -> 'a

let runOne b t = let (Beh bf) = b
                 in
                 bf t

// val runList : 'a Behavior -> Time list -> 'a list

let runList b t = let (Beh bf) = b
                  in
                  List.map bf t


// val runSeq : 'a Behavior -> seq<Time> -> seq<'a>

let runSeq b t = let (Beh bf) = b
                 in
                 Seq.map bf t