Codice and Circenses http://nicolas.biri.name/ Sat, 19 May 2018 00:00:00 UT Dependent types as tests http://nicolas.biri.name//posts/2018-05-19-Dependent-types-as-tests.html Posted on May 19, 2018 by Nicolas Biri

Types are not supposed to replace tests… in general. However, dependent types allow you to express tests as types, allowing you to run your tests automatically at compile times. Even better, you can express property tests and prove them rather than test them on a large subset of values.

To be accurate, we’re not talking about tests here, rather of proofs. I’m using tests just because what we’re checking can be compared to what we usually check with usual tests. Please don’t mind if I don’t use the appropriate term in favor of a more informal terms, I just want to prove to people that we can replace tests with a better alternative, with the appropriate language.

This article illustrates this possibility through the tennis kata. Our objective, provide a way to compute and display a tennis score (of only one game) correctly, and prove that our code is right. Let’s see how “if it compiles, you can ship it” can be real.

The code used in this article is available as a gist.

The tennis kata with dependent types

Types, types, types

First, let’s write some idris to encode the score progression and scores display.

To encode the score, we use the following rules. To win a game, a player must:

  • have scored at least four points;
  • and must be ahead by at least two points.

We start by the obvious, we need two players:

data Player = Player1 | Player2

A classical way to represent score is to use two naturals. This encoding however, allows a lot of invalid combination. For example, 5-0 is not a valid score, as the previous score, 4-0, should already have led to the end of the game.

A way to tackle this with dependent type is to embed in a score a proof that the game is not over. Let’s build a type that can prove that nobody has won yet:

||| Prove that the game is not over after a point
data NotWinning : (winnerScore: Nat) -> (loserScore: Nat) -> Type where
  ||| Game is not over because the winning player is below 40
  ThresholdNotMet : LTE x 3 -> NotWinning x y
  ||| Game is not over because we only reach deuce or advantage
  OpponentTooClose : LTE x (S y) -> NotWinning x y

The first constructor is a proof that nobody has won because the winner of a point has not reached 4 points yet (they have less or equal to 3 points). The second constructor is a proof that the winner of a point is not 2 points ahead yet.

Now that we have a type for our proofs, let’s write a decision procedure to build a proof or to prove the lack of proof that a game is over depending on a score:

||| Decision procedure to obtain a NotWinningProof
notWinning : (winnerScore : Nat)
              -> (loserScore : Nat)
              -> Dec (NotWinning winnerScore loserScore)
notWinning winnerScore loserScore =
  case isLTE winnerScore 3 of
       (Yes prfT)    => Yes (ThresholdNotMet prfT)
       (No contraT) => case isLTE winnerScore (S loserScore) of
                            Yes prfO => Yes (OpponentTooClose prfO)
                            No contraO => No (winningProof contraT contraO)
  where
    winningProof : (contraT : Not (LTE winnerScore 3))
                -> (contraO : Not (LTE winnerScore (S loserScore)))
                -> Not (NotWinning winnerScore loserScore)
    winningProof contraT contraO (ThresholdNotMet x) = contraT x
    winningProof contraT contraO (OpponentTooClose x) = contraO x

We check each condition that are necessary to build one of our constructors. If one of the two condition is met, we are able to provide our proof, otherwise we use the two witnesses that our conditions aren’t met to prove that we cannot build a witness that the game is not over (i.e., we have a winner).

Score

From there, we are able to define a type for the score of an ongoing game:

||| Score is built by stacking ball winners, along with the proof that the game is not over
data Score : Nat -> Nat -> Type where
  Start : Score 0 0
  Player1Scores : Score x y -> {auto prf: NotWinning (S x) y} -> Score (S x) y
  Player2Scores : Score x y -> {auto prf: NotWinning (S y) x} -> Score x (S y)

Our score type does not exactly handle the score. It’s rather a list of points winners, tied with the proof that the game is not over.

We’re almost ready to implement the function that allows a player to score a point. Before, we need a helper to compute the next score, given a point winner and a player, we compute the hypothetical next score of this player (if the game is not over):

nextScore : (winner : Player) -> (pointsOwner : Player) -> (points : Nat) -> Nat
nextScore Player1 Player1 points = S points
nextScore Player1 Player2 points = points
nextScore Player2 Player1 points = points
nextScore Player2 Player2 points = S points

And here we go with the scoring:

||| Add the result of a point to a score
score : (previousScore : Score p1Points p2Points)
      -> (p : Player)
      -> Maybe (Score (nextScore p Player1 p1Points)
                      (nextScore p Player2 p2Points))
score previousScore p {p1Points} {p2Points} =
  case notWinning (winnerPoints p) (loserPoints p) of
       Yes _      => pure (builder p)
       No  contra => 
  where
    winnerPoints : Player -> Nat
    winnerPoints Player1 = S p1Points
    winnerPoints Player2 = S p2Points
    loserPoints : Player -> Nat
    loserPoints  Player1 = p2Points
    loserPoints  Player2 = p1Points
    builder : (p : Player)
    -> {auto prf : NotWinning (winnerPoints p) (loserPoints p)}
           -> Score (nextScore p Player1 p1Points) (nextScore p Player2 p2Points)
    builder Player1 = Player1Scores previousScore
    builder Player2 = Player2Scores previousScore

The score function, given the winner of a point, will either returns the next score or nothing, if the winner of the point won the game. The code almost entirely relies on the notWinning function, which provides either the proof needed to build the next score, or the proof that we have a winner.

Then, we can replay a whole game quite easily with foldlM:

||| Replay a full list of points, if the game ends, the remaining points are discarded
replay : List Player ->  Either Player (x' ** y' ** Score x' y')
replay = foldlM f (_ ** (_ ** Start))
  where
    f : (a ** b ** Score a b) -> Player -> Either Player (x' ** y' ** Score x' y')
    f (_ ** _ ** s) p = maybe (Left p)
                              (pure . (\s' => (_ ** _ ** s')))
                              $ score s p

The result of a replay is either a winner, or an existential type (or Σ-type) with the current score. The existential type is required since we do not know in advance what the score will be.

Display

We’re almost there, we just need a few facilities to display the score: We start with a helper that displays points in the usual and weird tennis style. The points are announced only for the 3 first points of a player (before, the deuce and advantage part), thus we need a proof that we are less or equal to 3 to display the points:

displayPoints : LTE x 3 -> String
displayPoints LTEZero = "0"
displayPoints (LTESucc LTEZero) = "15"
displayPoints (LTESucc (LTESucc LTEZero)) = "30"
displayPoints (LTESucc (LTESucc (LTESucc LTEZero))) = "40"

And we can now display the whole score, starting with the special cases and then the general one:

displayScore : Score x y -> String
displayScore {x = Z} {y = Z} _ = "love"
displayScore {x = S (S (S Z))} {y = S (S (S Z))} _ = "deuce"
displayScore {x} {y} _ = case (isLTE x 3, isLTE y 3) of
  (Yes prfX,   Yes prfY)   => concat [displayPoints prfX
                                     , " - "
                                     , displayPoints prfY
                                     ]
  _                        => case compare x y of
                                   LT => "advantage Player2"
                                   EQ => "deuce"
                                   GT => "advantage Player1"

Tests

Isn’t this article supposed to be about testing? Well… here we are.

Unit testing

Let start with something simple. If we provide a way to replay an empty list of points, the score must be unchanged. A usual way to test it in a language without dependent type would be something like:

||| pseudocode, does not compile
replayEmptyListGivesStart : IO Bool
replayEmptyListGivesStart = assertEqual (Right (0 ** 0 ** Start)) (replay [])

Well, we can actually do better:

replayEmptyListGivesStart : Right (0 ** 0 ** Start) = replay []
replayEmptyListGivesStart = Refl

This is a compile time test. We have expressed here that replayEmptyListGivesStart is an element of the structural equality between Right (0 ** 0 ** Start) and replay []. We are even able to provide this element which is Refl. Behind the hood, the compiler will rewrite replay [], be able to reduce it Right (0 ** 0 * Start). And as Refl is of type a = a, it’s a correct value for this type and our program compiles!

We’ve just embedded in our program a proof that Right (0 ** 0 ** Start) = replay []. This proof will be run at each compilation, we can’t ship our code if this is not true anymore.

What would happen if the proof didn’t stand?

Let’s try and change the type of replayEmptyListGivesStart with the following: replayEmptyListGivesStart : Left Player1 = replay []

We obtain this error message:

/Users/biri/Perso/katas/Tennis/TennisDT.idr:133:29-32:
    |
133 | replayEmptyListGivesStart = Refl
    |                             ~~~~
When checking right hand side of replayEmptyListGivesStart with expected type
        Left Player1 = replay []

Type mismatch between
        Right (0 ** 0 ** Start) = Left Player1 (Expected type)
and
        x = x (Type of Refl)

Specifically:
        Type mismatch between
                Left Player1Unification failure
        and
                Right (0 ** 0 ** Start)

The compiler complains that we provide an element of type x = x while it was able to compute Left Player1 = Right (0 ** 0 ** Start). The output is quite precise about what went wrong here.

Similarly, we can test some specific results of our display function:

displayStartIsLove : "love" = displayScore Start
displayStartIsLove = Refl

display1stWinnerGot15 : (s : Score 1 0) -> "15 - 0" = displayScore s
display1stWinnerGot15 _ = Refl

Property testing

Let’s now test that our replay function got a love game right. A love game happens when a player won the 4 first points in a row, ending the game without leaving any point to their opponent.

Let write a simple function to test this:

testLoveGame : Left p = (p : Player) -> replay (replicate 4 p)

Given any player, we can prove that this player won the game when he or she won the 4 first points A first try to implement this function might be:

testLoveGame : (p : Player) -> Left p = replay (replicate 4 p)
testLoveGame p = Refl

Unfortunately, it doesn’t work. And I won’t provide you the error here, because it’s quite (very) verbose. The problem is that without knowing the value of p (the wining player), the compiler isn’t able to reduce the left term of the equality. Fortunately, splitting cases for the different values of Player is enough:

testLoveGame : (p : Player) -> Left p = replay (replicate 4 p)
testLoveGame Player1 = Refl
testLoveGame Player2 = Refl

Knowing the value of p, the compiler is now able to reduce the left term up to Left p, and thus to typecheck.

With the same strategy, we can check more advance properties, like ensuring that one player can’t win from deuce or that one player win if scores two times after a deuce:

testCantWinFromDeuce : (p : Player) -> True = isRight (replay (take 11 $ cycle [p, opponent p]))
testCantWinFromDeuce Player1 = Refl
testCantWinFromDeuce Player2 = Refl

gainWith2PointsFromDeuce : (p : Player) -> Left p = replay ((take 10 $ cycle [Player1, Player2]) <+> replicate 2 p)
gainWith2PointsFromDeuce Player1 = Refl
gainWith2PointsFromDeuce Player2 = Refl

Property testing with recursions

In the last examples, we used a arbitrary number as a parameter for take. Unfortunately, for these examples, testing the property for every number of points is bit complex. To illustrate how we can prove things on natural, let’s move to the display function again.

We will need a few helpers first, that are not in the base library:

compareToSuccIsLT : (x : Nat) -> LT = compare x (S x)
compareToSuccIsLT Z = Refl
compareToSuccIsLT (S k) = compareToSuccIsLT k

compareSameIsEq : (x : Nat) -> EQ = compare x x
compareSameIsEq Z = Refl
compareSameIsEq (S k) = compareSameIsEq k

compareToPredIsGT : (x : Nat) -> GT = compare (S x) x
compareToPredIsGT Z = Refl
compareToPredIsGT (S k) = compareToPredIsGT k

LTnotEQ : (LT = EQ) -> Void
LTnotEQ Refl impossible

LTnotGT : (Prelude.Interfaces.LT = GT) -> Void
LTnotGT Refl impossible

EQnotGT : (EQ = GT) -> Void
EQnotGT Refl impossible

DecEq Ordering where
  decEq LT LT = Yes Refl
  decEq LT EQ = No LTnotEQ
  decEq LT GT = No LTnotGT
  decEq EQ LT = No (negEqSym LTnotEQ)
  decEq EQ EQ = Yes Refl
  decEq EQ GT = No EQnotGT
  decEq GT LT = No (negEqSym LTnotGT)
  decEq GT EQ = No (negEqSym EQnotGT)
  decEq GT GT = Yes Refl

The helpers give us two things:

  1. the result of a comparison of a natural with it’s predecessor, itself, its successor;
  2. a structural proof of equality/difference between two Ordering

With these helpers, we will prove the display of advantages and deuce when a player is at most one point ahead of their opponent and each player has at least scored 3 times. To do so, we must find an implementation for these 3 functions:

displayDeuce : (x : Nat) -> (s : Score (3 + x) (3 + x)) -> displayScore s = "deuce"
displayAdvantageP1 : (x : Nat) -> (s : Score (S (3 + x)) (3 + x)) -> displayScore s = "advantage Player1"
displayAdvantageP2 : (x : Nat) -> (s : Score (3 + x) (S (3 + x))) -> displayScore s = "advantage Player2"

I’m detailing the first one here, the two others follow a similar pattern and are left as exercises to the reader. If you look back at display, you can notice that we don’t use the value of the score, only its type is used to compute the display. As a consequence, we don’t need to decompose score. Let’s try with what we’ve learned so far and decompose the first parameter:

displayDeuce : (x : Nat) -> (s : Score (3 + x) (3 + x)) -> displayScore s = "deuce"
displayDeuce Z _ = Refl
displayDeuce (S k) _ = Refl

Of course, we can’t decompose the parameter for all the possible naturals. So we use the decomposition for each possible constructors. Unfortunately, it’s not that easy. Here is the error we obtain with this try:

Type mismatch between
        "deuce" = "deuce" (Type of Refl)
and
        case Prelude.Nat.Nat implementation of Prelude.Interfaces.Ord, method compare n
                                                                                      n of
          LT => "advantage Player2"
          EQ => "deuce"
          GT => "advantage Player1" =
        "deuce" (Expected type)

The compiler is not able to reduce compare. Here is the tricky part. The solution is thus to match against the result of compare, using a with rule:

displayDeuce : (x : Nat) -> (s : Score (3 + x) (3 + x)) -> displayScore s = "deuce"
displayDeuce Z _ = Refl
displayDeuce (S k) s with (compare k k) proof p
  displayDeuce (S k) s | LT = ?lt_case
  displayDeuce (S k) s | EQ = Refl
  displayDeuce (S k) s | GT = ?gt_case

While we know that compare k k will always return EQ, the compiler has no way to figure it out. For the EQ case, knowing the result allows the compiler to achieve the reduction and we can conclude with Refl. The LT case and the GT case are symmetric. In both case, we have to prove that the case can’t happen.

Here is the information we have for ?lt_case:

  k : Nat
  s : Score (S (S (S (S k)))) (S (S (S (S k))))
  p : LT =
      Prelude.Nat.Nat implementation of Prelude.Interfaces.Ord, method compare k
                                                                               k
--------------------------------------
lt_Case : "advantage Player2" = "deuce"

So we need to find an element of type "advantage Player2" = "deuce" with a context that contains one very interesting element: p that is a “proof” that LT = compare k k

When you want to prove that something is not possible, you basically want to obtain a value of type Void and then conclude with the void function, that can build any type out of Void. Void is the bottom type, the one that is uninhabited. We have already some element to build such type. LTisNotEQ that we have introduced earlier, is of type Not (LT = EQ). This type is actually an alias for LT = EQ -> Void. And with p, we’re almost there, we just need to replace compare k k with it’s true value, EQ, we’re just one rewriting rule away. With the same reasoning for the GT case we obtain the following code for displayDeuce:

displayDeuce : (x : Nat) -> (s : Score (3 + x) (3 + x)) -> displayScore s = "deuce"
displayDeuce Z _ = Refl
displayDeuce (S k) s with (compare k k) proof p
  displayDeuce (S k) s | LT = void (LTnotEQ (rewrite compareSameIsEq k in p))
  displayDeuce (S k) s | EQ = Refl
  displayDeuce (S k) s | GT = void (negEqSym EQnotGT (rewrite compareSameIsEq k in p))

And here we go, it compiles and we have proven that if each player have scored at least 3 points and have the same number of points, their score will be “deuce”.

This short example also shows the limit of using types as tests. You’re doing more than testing, you are proving facts, which means that you must assist the compiler to find its way through the code to build the proof.

A few hints

Let’s recap some of useful hint if you want to use types as tests:

  1. Unit tests are the easiest, as most of the time, the compiler will be able to reduce the full expression if you provide all the required values.
  2. When you do property testing (proving actually), you almost always have to split cases for the variables, to ease the progression of the computer.
  3. When the compiler cannot reduce a value, help him with rewrite rules. Sometimes, it may be useful to tweak your test a bit to ease the reduction of a formula. For example, (x : Nat) -> (s : Score (3 + x) (3 + x)) -> "deuce" = displayScore s is easier to prove than (x : Nat) -> (s : Score (x + 3) (x + 3)) -> "deuce" = displayScore s because of the implementation of (+) for Natural.
  4. The error messages will help you figure out where the compiler is stuck and what expressions should be rewritten.
  5. If the compiler explores cases that are not possible, help it figure it out by producing a Void value.

I hope this helps. Unit testing is usually as easy with types than with traditional test functions, and property testing is usually harder, as it requires you to build a proof rather than just running the test on a limited set of values.

Have fun coding.

Acknowledgments

I would like to thanks the Lambdada community, who helped me to figure out how great the tests as type feature is, have heard the first version of my explanations and made some remarks to improve the code.

This blog has no comments, for any question or remark, feel free to contact me on twitter.

]]>
Sat, 19 May 2018 00:00:00 UT http://nicolas.biri.name//posts/2018-05-19-Dependent-types-as-tests.html Nicolas Biri
Le cas Facebook en détail http://nicolas.biri.name//posts/2016-08-20-Le-cas-Facebook-en-detail.html Posted on August 20, 2016 by Nicolas Biri

Le précédent billet était une réaction agacée, à chaud, à une tribune mensongère et contre-productive de la Quadrature du Net. Je sais, ce n’est pas la Quadrature dans son entièreté qui a porté cette tribune mais elle l’héberge et à ma connaissance, personne là bas n’a semblé trouver à redire sur les arguments de ce texte, je les associe de bon cœur.

Comme visiblement certains n’ont pas compris (et j’avoue que le titre mal choisi n’aidait pas), que mon but n’était pas de faire une critique ou une dithyrambe profonde du modèle de Facebook, je propose de le faire ici, histoire de poser les choses. Au passage, ça permettra d’expliciter quelles sont les arguments qui, à mes yeux, sont valables pour critiquer Facebook. En effet, il parait que l’on ne peut critiquer que si on a quelque chose à proposer. D’ailleurs, tant qu’on parle de cela, je rappelle à toute critique cinéma que la prochaine fois qu’il di du mal d’un film, ça serait bien qu’il propose un court métrage comme contre proposition.

Les vérités pénibles

Je ne vais pas refaire l’intégralité de mon précédent billet ici mais reprenons les principaux éléments qui m’ont agacés dans le billet publié par la Quadrature.

Facebook rend un service à ses utilisateurs

L’occulter et ne présenter que le gain que Facebook tire des données que ses utilisateurs y laissent (ce que sous entend, au bas mot, le terme de voleur) est un biais énorme. Soit c’est fait sciemment, auquel cas on essaie de tromper ses lecteurs. Soit l’auteur n’en a pas conscience et c’est une incompréhension majeure des internautes qu’il souhaite défendre. Je ne dis pas que chacun doit estimer que ce que permet Facebook est utile pour lui. Libre à chacun de douter de ce que ses services pourraient lui apporter personnellement. En revanche, il me semble difficile de nier que beaucoup trouvent les services proposés suffisamment intéressants pour tenir les utiliser régulièrement.

Il est normal que Facebook lutte contre le blocage de la publicité

Trouver totalement normal que les internautes puissent utiliser des moyens légaux pour contourner la publicité mais être choquer que les compagnies vivant de la publicité tentent de déjouer ces moyens est là aussi un point de vue stupide. Je comprends que ces tentatives (que l’on parle de celle de Facebook ou de celles de plusieurs sites d’informations) agacent. Elles sont néanmoins totalement logiques et fondées. Il est normal pour un acteur commercial d’utiliser les moyens légaux à sa disposition pour tenter de pérenniser ses profits.

Les mauvaises raisons pour utiliser un ad-blocker

En substance, le message à Facebook du billet de la Quadrature était : “il est normal que l’on puisse utiliser un ad-blocker si l’on n’est pas en phase avec votre modèle publicitaire.” Encore une fois, dans ce dont il est question ici, Facebook ne remet pas en cause cet usage, il cherche à le contourner légalement. Croyez bien que si l’article avait été sur le sujet de “Facebook cherche à interdire les bloqueurs de publicité”, ma réaction aurait été différente.

Mais réfléchissons un peu à la logique d’utiliser Facebook avec un bloqueur de publicité. L’auteur explique que ça ne remet pas en cause la santé financière de Facebook. Il a raison, ça ne la compromettrait que si tout le monde arrivait, efficacement, à bloquer ses publicités. Il est amusant qu’après mon premier billet sur le sujet, on m’aie reproché un point de vue de “sachant”. Admettons. Promouvoir l’utilisation d’un service sans accepter la contrepartie publicitaire, modèle uniquement viable parce que d’autres l’acceptent ou ne savent pas comment se passer de la publicité, ce ne serait pas également un point de vue de sachant ?

En continuant à utiliser un service (notamment un service communautaire) avec un ad-blocker, alors que vous savez parfaitement que vous ne l’utiliseriez pas sinon, vous renforcez ce service, l’intérêt que ceux qui n’utilisent pas ce service ont à l’utiliser et donc vous mettez ceux qui ne connaissent pas les bloqueurs de publicité dans une situation encore plus confortable. Votre sollicitude à l’égard des non-sachants me touche.

La critique de Facebook

N’ayant pas parler de ce qui me gêne dans le fonctionnement de Facebook la dernière fois, on m’a taxé de simplisme ou de défendre Facebook. Alors soit, ne nous contentons pas de critiquer les mauvais arguments et proposons quelques axes de critiques non exhaustifs.

Le pistage hors du site

Si j’estimais normal qu’un utilisateur de Facebook doive avoir conscience que les données qu’il y partage soient utilisées par le site (même s’il peut être bon de le rappeler de façon non-biaisée). Je trouve bien moins normal que Facebook piste les mouvements des internautes (utilisateurs ou non) pour monétiser son activité. En effet, losrsque je suis sur Facebook, je sais (ou devrais savoir) que ce que je fais lui est connu. Lorsque je visite un site tiers ayant un lien (parfois minime) avec Facebook, c’est bien moins le cas. L’acceptation implicite des conditions d’utilisation dont je parlais dans le billet précédent n’est plus de mise ici.

Tout le monde a dû voir ce message pénible et obligatoire indiquant l’utilisation de cookies sur la plupart des sites web. Ce qui est fortement ignoré, c’est que les cookies en questions ne sont pas uniquement ceux du site visités, mais également potentiellement tous ceux des sites qui sont intégrés au contenu du dit site.

Prenons un exemple : je visite mon site de jeux de société préféré et celui-ci intègre sur sa page un bouton pour “partager le contenu sur Facebook”. Ce bouton est fourni par Facebook et le simple fait de l’afficher permet à Facebook de savoir que vous avez visité cette page.

Cette pratique est légale, oui, mais fortement critiquable. Ce modèle, consistant à pister les gens de manière cachée, même lorsqu’ils ne sont pas sur votre site, est certainement le point le plus important à dénoncer dans la monétisation des profils des internautes (et en terme de confidentialité des données personnelles.)

Pour autant, même avec cet argument (qui me fait avoir très peu de considération pour Facebook), je ne les traiterai pas de voleur, parce que ce n’est pas le cas.

Les autres pistes ?

Facebook est critiquable sur sa diligence à bloquer ses utilisateurs pour (un peu) de nudité alors qu’il est moins rapide à bloquer des contenus haineux. Ou à sa volonté de bloquer certains contenus pour éviter d’être mal vus de certains pouvoir. Je comprends que ça puisse être choqué et dénoncé. Pour ma part, c’est juste une bonne raison de ne pas utiliser leur service car après tout, c’est le meilleur moyen pour lutter contre eux.

Il y a certainement d’autres critiques légitimes, je n’ai pas envie de creuser, il y a déjà là suffisamment pour ne pas utiliser des arguments fallacieux.

Conclusion

Voilà, avec un peu plus de calme, des arguments plus étayés de ce qui m’a énervé dans cette tribune de la Quadrature. Estimez maintenant si, en relisant cette tribune, l’avis qui y est porté propose des arguments valides et s’il touche au but ou non. Vous connaissez mon avis, faites vous le votre.

Ce genre de divergence est la raison principale qui fait que je n’apprécie pas la Quadrature du Net. Leur envie de dénoncer un système, souvent à juste titre, leur fait parfois utiliser des arguments fallacieux ou renier certains principes qui me sont importants.

]]>
Sat, 20 Aug 2016 00:00:00 UT http://nicolas.biri.name//posts/2016-08-20-Le-cas-Facebook-en-detail.html Nicolas Biri
Ne plus être le produit http://nicolas.biri.name//posts/2016-08-17-Ne-plus-etre-un-produit.html Posted on August 17, 2016 by Nicolas Biri

Aujourd’hui, sur ma timeline twitter, a circulé un article de la Quadrature intitulé “Si vous êtes le produit, ce n’est pas gratuit”. Je suis très souvent critique sur la forme des prises de positions de la quadrature, que je trouve trop souvent enfermée dans une opposition caricaturale. Cet article est pire, car il est rempli d’arguments que je trouve, faux, injustes et mensongers. Du coup, je prends le temps d’expliquer pourquoi.

Le modèle commercial “gratuit”

La premier partie, très juste, critique le mantra “si c’est gratuit, c’est vous le produit” et propose d’y substituer le plus juste “si vous êtes le produit, ce n’est pas gratuit”.

Jusque là, tout va bien.

Et là, à peine plus loin, on commence à s’éloigner de ce que je juge pertinent :

vous acceptez des contrats d’utilisation léonins qui font de vous une main d’œuvre sans droit ni titre, vous acceptez d’être pistés, tracés, traqués pour que le client final (généralement une régie publicitaire) sache tout de vous pour mieux vous cibler.

Le choix du vocabulaire fait tout pour placer le consommateur de services monétisant le contenu de ses utilisateurs et vivant de la publicité comme une victime. Je comprends que l’on rappelle quel est le mode de fonctionnement de ces sociétés, la limitation des droits que le contrat d’utilisation implique.

Il est toutefois important de rappeler que beaucoup, en conscience, acceptent ces contraintes. Parce qu’ils estiment que la contrepartie fait que l’accord est bon. Prenons mon cas, je suis un gros utilisateur de Twitter, service commercial qui utilise le contenu que je publie pour déterminer quelles publicités peuvent m’intéresser et qui a droit de vie et de mort sur mon compte. Je le sais et je l’accepte. Et je ne me sens ni traqué, ni ciblé, ni signataire d’un contrat léonins.

Il y a, chez les défenseurs des libertés, une volonté de convaincre les gens qu’il ne faut pas utiliser ces services qui m’a toujours gêné.

L’escroquerie de l’accusation de vol

Continuons avec une instance de service qui monétise les données de ses utilisateurs avec l’exemple de Facebook. Je me permets de recopier complètement le passage concerné :

Dans une conférence d’il y a longtemps, j’expliquais que Facebook était le plus grand voleur que je connaisse. Pour résumer : Facebook publie vos contenus (sans vous payer) pour attirer du public vers les écrans de publicité qu’il vend. Il revend (sans les payer) vos données personnelles à ses clients (les régies publicitaires) pour qu’elles puissent mieux vous cibler. Puis il vous propose – à vous – de payer pour que vos contenus soient plus visibles que les autres (et donc pour attirer plus de visiteurs vers les écrans de pub de ses clients), avant finalement de vous proposer d’acheter ses actions pour espérer enfin toucher une part du fric que votre travail rapporte.

C’est là que je me suis dit que non, jamais je ne pourrais supporter la quadrature. Je n’aime pas Facebook, j’y ai un compte dormant mais j’utilise très peu le service. Pourtant, je peux affirmer sans crainte que tout ce passage est la charge la plus honteuse et malhonnête que j’ai pu lire sur ce service. Détaillons.

  • Facebook publie vos > contenus (sans vous payer) pour attirer du public vers les écrans de publicité qu’il vend. : Oui, Facebook ne vous paye pas. Facebook vous rend un service qu’il paye avec la publicité. Ce service est de vous permettre de publier votre contenu et de le rendre disponible à vos contacts. Si vous jugez que ce service n’en vaut pas la peine, libre à vous de vous en passer.
  • Il revend (sans les payer) vos données personnelles à ses clients (les régies publicitaires) pour qu’elles puissent mieux vous cibler. Oui. ça fait partie là aussi de la contrepartie pour le service rendu.
  • Il vous propose – à vous – de payer pour que vos contenus soient plus visibles que les autres. Si vous payez, c’est soit que vous estimez que votre contenu est tellement intéressant qu’il doit être visible, soit que vous espérez que ça soit rentable pour vous. Dans tous les cas, si vous décidez de donner votre argent à Facebook, c’est que la contrepartie vous semble intéressante. On est loin du vol.

Bref, parce que l’auteur ne juge pas le service intéressant par rapport ses exigences et attentes, Facebook est un voleur, le plus grand voleur. J’ai pour ma part l’impression que la contrepartie offerte par Facebook est plus intéressante que celle offerte dans toutes les histoires de vol que j’ai entendu.

Et vous en redemandez. Il est toujours bon de souligner les phrases anodines qui tentent de prendre l’ascendant sur le lecteur : si vous utilisez un service qui vous vole, vous n’êtes pas bien malin.

L’injustice des mesures anti-blocage

Si on parle de Facebook, ce n’est pas juste pour vomir sur leur service, c’est également pour dénoncer les mesures anti-adblock prisent par le site : Facebook a visiblement depuis quelques temps modifier son code pour passer aux travers des filtres des bloqueurs de publicité.

Visiblement c’est choquant. Très. Plus que l’existence de logiciel qui bloquent la publicité. Laissons de coté pour le moment ce que je pense de la publicité sur internet. Si on trouve normal que les gens essaient de ne pas afficher les publicités, ne doit-on pas trouver également normal que les sites dont c’est le fond de commerce essaient d’être plus malin qu’eux ? Je dirais que si, mais je ne suis pas un expert des libertés (car visiblement, c’est de ça dont il s’agit.)

Ce préambule mis en place, regardons ce que l’article nous dit du modèle de la publicité sur internet. Elle est devenue envahissante au point d’avoir suscité l’émergence des dits ad-blockers. C’est un point de vue. On peut aussi juger que ce qui a popularisé les ad-blockers, c’est la volonté de continuer à utiliser des sites dont on ne supportait pas la logique commerciale. Le dire comme ça, c’est malheureusement culpabiliser l’utilisateur plutôt que le site et ça ne nous arrange pas. C’est donc mal.

le modèle économique « publicité contre fausse gratuité » est devenu tel qu’il remet en cause un tas de libertés fondamentales. Et c’est reparti.

La liberté d’expression (il faut que l’espace publicitaire reste assez propre pour attirer les annonceurs, cachez ce sein qu’ils ne sauraient voir). Alors autant je trouve la censure de Facebook, entre autres, ridicule, autant y associer la liberté d’expression me troue le cul. Donc non, on ne publie pas sur un site tiers ce qu’on veut, ça fait là aussi partie du compromis que l’on accepte en utilisant le service. Oui, je trouve honteux que Facebook soit choqué par l’érotisme. Mais je le sais, c’est connu et ce n’est pas une putain d’entrave à la liberté d’expression. Un nichon qui est censuré par facebook aura sa place dans plein d’autres endroit du web. Je trouve également cocasse d’estimer que ne pas pouvoir publier sur Facebook est synonyme d’entrave à la liberté d’expression, c’est donner beaucoup de pouvoir à un site qui est le plus grand des voleurs.

Les ad-blocks et la pause pipi

Le coup de grâce, dans ce tombereau d’inepties, vient quand on finit par comparer un ad-blocker à une pause pipi devant la télé. Pas le pire, certes, mais la fatigue aidant, un argument à la con en plus passe mal. Il s’agit tout d’abord de comparer internent, un média essentiellement écrit (surtout avec l’exemple cité de Facebook), à un média exclusivement vidéo. Ensuite, quand vous allez aux toilettes, la publicité passe quand même, pas quand vous la bloquez. Enfin, la télévision à plein d’astuces pour vous incitez à regarder la publicité (coupure avant un moment crucial, mini pause par exemple.) Il n’y a pas de comparaison sur internet… Ah si, contourner l’ad-blocker, cette idée même qui choque l’auteur.

Ma relation à la publicité sur internet

Je n’utilise plus d’ad-blocker depuis plusieurs années, j’utilise uniquement un tracker (ghostery), pour éviter aux sites de me pister.

Je n’utilise pas d’ad-blocker parce que j’estime que si je visite un site, je me dois d’accepter le contrat implicite qui me lie à ce site. De plus, cela à un gros avantage : si un site place trop de publicité, je m’en rends compte et j’arrête de m’y rendre. C’est la façon la plus saine pour ne pas encourager des pratiques commerciales qu’on désapprouve.

En revanche, j’utilise un outil anti-tracking car si j’accepte le contrat implicite qui me lie au site que je visite, je refuse d’accepter celui des sites qu’il intègre si je ne décide pas explicitement de les visiter.

Il est peut-être important vu la teneur de ce qui précède de bien souligner qu’il s’agit de ma façon d’interagir, pas de ce que j’estime nécessaire que tout le monde fasse.

]]>
Wed, 17 Aug 2016 00:00:00 UT http://nicolas.biri.name//posts/2016-08-17-Ne-plus-etre-un-produit.html Nicolas Biri
Union Type in Idris (Part 5) http://nicolas.biri.name//posts/2016-08-14-union-type-in-idris-part-5.html Posted on August 14, 2016 by Nicolas Biri

Go back to the first part.

Unit and property testing in Idris

I thought I was done with the union type series, but then I think about how great this example is to demonstrate some of the possibilities of test and property checking in Idris.

Idris has a basic testing framework. I won’t discuss it here. As Idris is very young, the framework has little interest at this time and we can do way better (in my opinion) by just using types in a clever way.

Unit testing at compile time

As types are a first class language construct in Idris, you can use value to build type. An interesting type constructor is =. The type a = b is populated by the proof that a is euqal to be b. To construct inhabitants of this class, we often use Refl, which is an inhabitant of a = a (We already used Refl in the definitions of isomorphisms in part 4.)

This can be used to define unit testing functions, that will be resolved at compile time. If we go back to the definition of Union and ot the member constructor

data Union : List Type -> Type where
  MemberHere : ty -> Union (ty::ts)
  MemberThere : Union ts -> Union (ty::ts)

member : ty -> {auto e: Elem ty ts} -> Union ts
member x {e = Here} = MemberHere x
member x {e = There later} =
  MemberThere (member x {e = later})

We can quickly set up some tests:

memberCreateMemberHereTest :
  the (Union [String, Nat]) (member "Foo") =
  MemberHere "Foo"
memberCreateMemberHereTest = Refl

and:

memberCreateMemberThereTest :
  the (Union [String, Nat]) (member 42) =
  MemberThere (MemberHere 42)
memberCreateMemberThereTest = Refl

These tests will be executed at compile time and will pass if our definition are correct. No need for a framework.Moreover, if we ship a faulty version without testing, thrd users won’t be able to use the library because they won’t be able to compile our code.

Theorem proving (or enhanced property testing)

Tests prove that your definitions are correct for some values, theorem prove that it’s correct for all values. Even with something as simple as the Union library, we can prove fancy stuff.

Let’s get a member

First, let’s recall the definition of get:

get : Union ts -> {auto p: Elem ty ts} -> Maybe ty
get (MemberHere x)      {p = Here}      = Just x
get (MemberHere x)      {p = There _}   = Nothing
get (MemberThere x)     {p = Here}      = Nothing
get (MemberThere later) {p = (There l)} = get later {p = l}

It’s quite natural to see get as the invert of member. Let’s formalise it with some properties.

To warm up, we’ll write a first one that just work for a trivial case: one type union. We want to check that if we build a one typ uninon with member and get ot back, we obtain Just. It’s as simple as writing this description:

getHereMemberIsJust : (x : a) -> get (member x) = Just x
getHereMemberIsJust _ = Refl

Let’s go further and prove something similar for more complex unions. To do so, we must be able to reason on which type we populated. To do so, we will make the Elem ty ts implicit parameter of member and get explicit.

If we create a union with member at a place given by Elem ty ts and check the value at the same place with get, we should have Just the value:

getMemberWithElemIsJust :
  (x : a) ->
  (p : Elem a xs) ->
  the (Maybe a) (get (member x {p}) {p}) = Just x

This prove is trickier thant the previous one. It should be proved by induction on p. The base case is easy. if p = Here, the compiler will reduce both part of the equality by itself and we only has to prove a straight forward equality:

getMemberWithElemIsJust _ Here = Refl

The induction case is quite easy too now thanks to the previous case hypothesis.

It’s time for me to introduce the interactive proof facilities of Idris. Actually, I should have done it way earlier but it’s one of this thing that seems so natural to use that you forgot to mention how great it is.

The Idris REPL, and many compatible editors, provide some helpful command to ease type driven development. One of them is being able to define holes in your code and to check their type. More details about TDD and interactive development in Idris can be found in the online documentation or in the excellent Idris book.

Now that you know it, we can use hole in our induction case:

getMemberWithElemIsJust x (There later) = ?wut

?wut is our hole, and if we ask to the compiler its type, we obtain:

  a : Type
  x : a
  xs : List Type
  later : Elem a xs
  y : Type
--------------------------------------
wut : get (member x) = Just x

Idris provides a recap of all the types in the context, and display the expected type for wut. With the context information, Idris was able, by itself, to reduce wut to the type of the induction hypothesis. Thus, we just have to provide it:

getMemberWithElemIsJust x (There later) =
  getMemberWithElemIsJust x later

And that’s it. We have a complete proof that if we put a value at a given place of a union and if we get the value at the same place, we obtain Just this value.

The difficulty of theorem proving (digression)

The above proofs are almost trivial when you are used to types and to theorem proving. However, it may be hard to grasp for newcomers. Actually, these small examples can be used to illustrate the difficulties encountered by the advocates of formally proved code to convince developpers and the industry.

To be honest, I had few experiences with formal proof education to devleopers. However, during these experiences, I encountered two majors objections:

  1. The properties I want to test are not complex enough to require a whole proof, proving that it works for some values, or for a set of random value is sufficient.
  2. When I have to encode a proof, I have to formalize a work that I have already done mentally when I wrote the code, in a non-natural way.

Both objections are unfortunately valid in most cases. We can however mitigates this feeling by:

  1. What if, for almost the same costs, as unit or properties tests, you can be sure that it is valid for all the values.
  2. Providing helpful enough programing environment to mae the formalisation process as natural as possible.

Moreover, I should emphasize one of the major advantages of formal proof embedded in a library, that I’ve already mentioned in the article. These proofs of properties can be seen as a contract that my library ensure to respect. It offers garantees about third party code and thus can higly reduce the lack of confidence that I can have in those libraries.

]]>
Sun, 14 Aug 2016 00:00:00 UT http://nicolas.biri.name//posts/2016-08-14-union-type-in-idris-part-5.html Nicolas Biri
Union Type in Idris (Part 4) http://nicolas.biri.name//posts/2016-08-02-union-type-in-idris-part-4.html Posted on August 2, 2016 by Nicolas Biri

Go back to the first part.

A glimpe of equality

Idris handles equality with a typeclass, just like Haskell. Whilst equality must be define specifically for each sum type, we can define the equality in a generic manner for union types. The idea is to define the equality iteratively on the list of types of the union.

Equality for Union x::xs

Let’s start with the recursive case, do we really need to elaborate?

(Eq ty, Eq (Union xs)) => Eq (Union (ty::xs)) where
  (==) (MemberHere x) (MemberHere y) = x == y
  (==) (MemberHere x) (MemberThere y) = False
  (==) (MemberThere x) (MemberHere y) = False
  (==) (MemberThere x) (MemberThere y) = x == y

We need equality for the previous union (induction) nd for the newly added type. Then, two terms are equal if they point to the same type and have the same value.

The base case

Things here are a little trickier. We nned an instance of Eq (Union []). Let’s look back at the Union definition:

data Union : List Type -> Type where
  MemberHere : (x: ty) -> Union (ty::xs)
  MemberThere : (x: Union xs) -> Union (ty::xs)

Do you see the potential issue? None of them allow us to build a Union []. It’s impossible to build an element of Union []? Just explain it to Idris:

Eq (Union []) where
  (==) (MemberHere _) _ impossible
  (/=) (MemberThere _) _ impossible

Profit:

> the (Union [Nat, String]) "foo" = the (Union [Nat, String]) "foo"
True : Boolean
> the (Union [Nat, String]) "foo" = the (Union [Nat, String]) "bar"
False : Boolean
> the (Union [Nat, String]) "foo" = the (Union [Nat, String]) 42
False : Boolean

Uninhabited types

There is an alternative to the impossible solution. We can claim that a type is uninhabited thanks to a typeclass:

Uninhabited (Union []) where
    uninhabited (MemberHere _) impossible
    uninhabited (MemberThere _) impossible

Uninhabited comes with an absurd funciton of type Uninhabited e => e -> a absurd can be read as: if we have an instance of an uninhabited type, we can build anything from it.

Thanks to Uninhabited, our instance of Eq can be written:

Eq (Union []) where
  (==) x _ = absurd x

Unions and synonyms

If our unions are well defined, a union of a unique type should be isomorphic to this type. A union of two types should be isomorphic to Either. Let’s transform these assumptions into proofs.

Morphisms

The morphisms can be implemented as instances of the Cast typeclass and are straightforward as soon as we know how to deal with uninhabited types:

Cast (Union [ty]) ty where
  cast (MemberHere x) = x
  cast (MemberThere x) = absurd x

Cast l (Union [l]) where
  cast x = (MemberHere x)

Cast (Union [l, r]) (Either l r) where
  cast (MemberHere x) = Left x
  cast (MemberThere (MemberHere x)) = Right x
  cast (MemberThere (MemberThere x)) = absurd x

Cast (Either l r) (Union [l, r]) where
  cast (Left x) = (MemberHere x)
  cast (Right x) = (MemberThere (MemberHere x))

Put an iso in our morphisms

Idris has a Iso type, that is use to prove that two types are isomorphic. Here is its definition:

data Iso : Type -> Type -> Type
  MkIso : (to : a -> b)
        -> (from : b -> a)
        -> (toFrom : (y : b) -> to (from y) = y)
        -> (fromTo : (x : a) -> from (to x) = x)
        -> Iso a b

to and from are the two morphisms used to build the isomorphism. toFrom and fromTo are proofs. They are populated if and only if for (to . from) and (from . to) behave like the id function.

Thus, being able to build an instance of Iso a b is a proof that a and b are isomorphic. Let’s buils these instances for Union:

oneTypeUnion : Iso (Union [ty]) ty
oneTypeUnion = MkIso cast cast toFrom fromTo
  where
    toFrom _ = Refl
    fromTo (MemberHere _) = Refl
    fromTo (MemberThere x) = absurd x

And:

eitherUnion : Iso (Union [l, r]) (Either l r)
eitherUnion = MkIso cast cast toFrom fromTo
  where
    toFrom (Left _) = Refl
    toFrom (Right _) = Refl
    fromTo (MemberHere _) = Refl
    fromTo (MemberThere (MemberHere _)) = Refl
    fromTo (MemberThere (MemberThere x)) = absurd x

Refl is a member of the type x = x. Being able to compile this code build a formal proof of the existence of the isomorphisms.

Part 4 is over

In the next part, we’ll discuss union type testing. And, by the way, the code is on github.

]]>
Tue, 02 Aug 2016 00:00:00 UT http://nicolas.biri.name//posts/2016-08-02-union-type-in-idris-part-4.html Nicolas Biri