Joachim Breitner: Compose Conference talk video online

From: Sebastian R. < @gmail.com>Nice. I like when people learn from my lectures. The introduction is a bit werid, but ok, maybe this guy had some bad experiences. Strangley, I don t see a mistake in the material, so I respond:
To: joachim@cis.upenn.edu
Subject: re: Errors I've spotted a basic error in your course on Haskell (https://www.seas.upenn.edu/~cis194/fall16/). Before I proceed, it's cool if you're not receptive to errors being indicated; I've come across a number of professors who would rather take offense than admit we're all human and thus capable of making mistakes... My goal is to find a resource that might be useful well into the future, and a good indicator of that is how responsive the author is to change. In your introduction note you have written:n contrast to a classical intro into Haskell, we do not start with numbers, booleans, tuples, lists and strings, but we start with pictures. These are of course library-defined (hence the input CodeWorld) and not part of the language . But that does not make them less interesting, and in fact, even the basic boolean type is library defined it just happens to be the standard library.Howeverm there is noinput CodeWorld
in the code above. Have you been made aware of this error earlier? Regards, ...
From: Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript>A while later, I receive this response:
To: Sebastian R. < @gmail.com>
Subject: Re: Errors Dear Sebastian, thanks for pointing out errors. But the first piece of code under Basic Haskell starts withso I am not sure what you are referring to. Note that these are lecture notes, so you have to imagine a lecturer editing code live on stage along with it. If you only have the notes, you might have to infer a few things. Regards, Joachim-# LANGUAGE OverloadedStrings #- import CodeWorld
From: Sebastian R. < @gmail.com>I am a bit reminded of Sean Spicer they re not my words! but clearly I am missing something. And indeed I am: In the code snippet, I wrote correctly
To: Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript>
Subject: Re: Errors Greetings, Joachim. Kindly open the lecture slides and search for "input CodeWorld" to find the error; it is not in the code, but in the paragraph that implicitly refers back to the code. You might note that I quoted this precisely from the lectures... and so I repeat myself... this came from your lectures; they're not my words!In contrast to a classical intro into Haskell, we do not start with numbers, booleans, tuples, lists and strings, but we start with pictures. These are of course library-defined (hence the input CodeWorld) and not part of the language . But that does not make them less interesting, and in fact, even the basic boolean type is library defined it just happens to be the standard library.This time around, I've highlighted the issue. I hope that made it easier for you to spot... Nonetheless, I got my answer. Don't reply if you're going to fight tooth and nail about such a basic fix; it's simply a waste of both of our time. I'd rather learn from somewhere else... On Tue, Aug 1, 2017 at 11:19 PM, Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript> wrote:
import CodeWorld
, but in the text I had input CodeWorld
. I probably did write LaTeX before writing the lecture notes. Well, glad to have that sorted out. I fixed the mistake and wrote back:
From: Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript>(And it seems I now made the inverse typo, writing import instead of input . Anyways, I did not think of this any more until a few days later, when I found this nice message in my mailbox:
To: Sebastian R. < @gmail.com>
Betreff: Re: Errors Dear Sebastian, nobody is fighting, and I see the mistake now: The problem is not that the line is not in the code, the problem is that there is a typo in the line and I wrote input instead of import . Thanks for the report, although you did turn it into quite a riddle a simple you wrote import when it should have been import would have been a better user of both our time. Regards, Joachim Am Donnerstag, den 03.08.2017, 13:32 +1000 schrieb Sebastian R.:
From: Sebastian R. < @gmail.com>Well, I chose to be amused by this, and I am sharing my amusement with you.
To: Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript>
Subject: Re: Errorsa simple you wrote import when it should have been import would have been a better user of both our time.We're both programmers. How about I cut ALL of the unnecessary garbage and just tell you to s/import/input/ on that last quotation (the thing immediately before this paragraph, in case you didn't know). I blatantly quoted the error, like this:In your introduction note you have written:Since that apparently wasn't clear enough, in my second email to you I had to highlight it like so:n contrast to a classical intro into Haskell, we do not start with numbers, booleans, tuples, lists and strings, but we start with pictures. These are of course library-defined (hence the input CodeWorld) and not part of the language . But that does not make them less interesting, and in fact, even the basic boolean type is library defined it just happens to be the standard library.Howeverm there is noinput CodeWorld
in the code above.You might note that I quoted this precisely from the lectures... and so I repeat myself... this came from your lectures; they're not my words!I'm not sure if you're memeing at me or not now, but it seems either your reading comprehension, or your logical deduction skills might be substandard. Unfortunately, there isn't much either of us can do about that, so I'm happy to accept that some people will be so stupid; after all, it's to be expected and if we don't accept that which is to be expected then we live our lives in denial. Happy to wrap up this discusson here, Seb... On Fri, Aug 4, 2017 at 12:22 AM, Joachim Breitner <noscript>joachim at cis dot upenn dot edu</noscript> wrote:In contrast to a classical intro into Haskell, we do not start with numbers, booleans, tuples, lists and strings, but we start with pictures. These are of course library-defined (hence the input CodeWorld) and not part of the language . But that does not make them less interesting, and in fact, even the basic boolean type is library defined it just happens to be the standard library.This time around, I've highlighted the issue. I hope that made it easier for you to spot...
datatype 'a list = nil cons (hd : 'a) (tl : "'a list")
with the well-known induction principle that many of my readers know by heart (syntax slightly un-isabellized):
P nil ( x xs. P xs P (cons x xs)) xs. P xs
codatatype 'a llist = lnil lcons (hd : 'a) (tl : "'a llist")
we get the following coinduction principle:
( xs ys.
R xs ys' (xs = lnil) = (ys = lnil)
(xs lnil ys' lnil
hd xs = hd ys R (tl xs) (tl ys)))
( xs ys. R xs ys xs = ys)
This is less scary that it looks at first. It tell you if you give me a relation R
between lists which implies that either both lists are empty or both lists are nonempty, and furthermore if both are non-empty, that they have the same head and tails related by R
, then any two lists related by R
are actually equal.
If you think of the infinte list as a series of states of a computer program, then this is nothing else than a bisimulation.
So we have two proof principles, both of which make intuitive sense. But how are they related? They look very different! In one, we have a predicate P
, in the other a relation R
, to point out just one difference.
datatype
declaration automatically creates a relator:
rel_list :: ('a 'b bool) 'a list 'b list bool
The definition of rel_list R xs ys
is that xs
and ys
have the same shape (i.e. length), and that the corresponding elements are pairwise related by R
. You might have defined this relation yourself at some time, and if so, you probably introduced it as an inductive predicate. So it is not surprising that the following induction principle characterizes this relation:
Q nil nil
( x xs y ys. R x y Q xs ys Q (cons x xs) (cons y ys))
( xs ys rel_list R xs ys Q xs ys)
Note how how similar this lemma is in shape to the normal induction for lists above! And indeed, if we choose Q xs ys (P xs xs = ys)
and R x y (x = y)
, then we obtain exactly that. In that sense, the relation induction is a generalization of the normal induction.
codatatype
declaration introduces a function
rel_llist :: ('a 'b bool) 'a llist 'b llist bool
which relates lists of the same shape with related elements only that this one also relates infinite lists, and therefore is a coinductive relation. The corresponding rule for proof by coinduction is not surprising and should remind you of bisimulation, too:
( xs ys.
R xs ys (xs = lnil) = (ys = lnil)
(xs lnil ys lnil
Q (hd xs) (hd ys) R (tl xs) (tl ys)))
( xs ys R xs ys rel_llist Q xs ys)
It is even more obvious that this is a generalization of the standard coinduction principle shown above: Just instantiate Q
with equality, which turns rel_llist Q
into equality on the lists, and you have the theorem above.
( xs ys.
R xs ys (xs = lnil) = (ys = lnil)
(xs lnil ys lnil
Q (hd xs) (hd ys) R (tl xs) (tl ys)))
= case analysis (the other two cases are vacuously true)
( xs ys.
xs = lnil ys = lnil
R xs ys (xs = lnil) = (ys = lnil)
(xs lnil ys lnil
Q (hd xs) (hd ys) R (tl xs) (tl ys)))
( xs ys.
xs lnil ys lnil
R xs ys (xs = lnil) = (ys = lnil)
(xs lnil ys lnil
Q (hd xs) (hd ys) R (tl xs) (tl ys)))
= simplification
( xs ys. xs = lnil ys = lnil R xs ys
( x xs y ys. R (cons x xs) (cons y ys) (Q x y R xs ys))
= more rewriting
R nil nil
( x xs y ys. Q x y R xs ys R (cons x xs) (cons y ys))
CoInductive ENat :=
N : ENat
S : ENat -> ENat.
CoFixpoint min (n : ENat) (m : ENat)
:=match n, m with S n', S m' => S (min n' m')
_, _ => N end.
CoInductive le : ENat -> ENat -> Prop :=
leN : forall m, le N m
leS : forall n m, le n m -> le (S n) (S m).
The lemma is specified as
Lemma min_le: forall n m, le (min n m) n.
and the proof method of choice to show that some coinductive relation holds, is cofix
. One would wish that the following proof would work:
Lemma min_le: forall n m, le (min n m) n.
Proof.
cofix.
destruct n, m.
* apply leN.
* apply leN.
* apply leN.
* apply leS.
apply min_le.
Qed.
but we get the error message
Error:
In environment
min_le : forall n m : ENat, le (min n m) n
Unable to unify "le N ?M170" with "le (min N N) N
Effectively, as Coq is trying to figure out whether our proof is correct, i.e. type-checks, it stumbled on the equation min N N = N
, and like a kid scared of coinduction, it did not dare to run the min
function. The reason it does not just run a CoFixpoint
is that doing so too daringly might simply not terminate. So, as Adam explains in a chapter of his book, Coq reduces a cofixpoint only when it is the scrutinee of a match
statement.
So we need to get a match
statement in place. We can do so with a helper function:
Definition evalN (n : ENat) :=
match n with N => N
S n => S n end.
Lemma evalN_eq : forall n, evalN n = n.
Proof. intros. destruct n; reflexivity. Qed.
This function does not really do anything besides nudging Coq to actually evaluate its argument to a constructor (N
or S _
). We can use it in the proof to guide Coq, and the following goes through:
Lemma min_le: forall n m, le (min n m) n.
Proof.
cofix.
destruct n, m; rewrite <- evalN_eq with (n := min _ _).
* apply leN.
* apply leN.
* apply leN.
* apply leS.
apply min_le.
Qed.
ENat
and le
:
theory ENat imports Main begin
codatatype ENat = N S ENat
primcorec min where
"min n m = (case n of
N N
S n' (case m of
N N
S m' S (min n' m')))"
coinductive le where
leN: "le N m"
leS: "le n m le (S n) (S m)"
There are actually many ways of defining min
; I chose the one most similar to the one above. For more details, see the corec
tutorial.
Now to the proof:
lemma min_le: "le (min n m) n"
proof (coinduction arbitrary: n m)
case le
show ?case
proof(cases n)
case N then show ?thesis by simp
next
case (S n') then show ?thesis
proof(cases m)
case N then show ?thesis by simp
next
case (S m') with n = _ show ?thesis
unfolding min.code[where n = n and m = m]
by auto
qed
qed
qed
The coinduction
proof methods produces this goal:
proof (state)
goal (1 subgoal):
1. n m. ( m'. min n m = N n = m')
( n' m'.
min n m = S n'
n = S m'
(( n m. n' = min n m m' = n) le n' m'))
I chose to spell the proof out in the Isar proof language, where the outermost proof structure is done relatively explicity, and I proceed by case analysis mimiking the min
function definition.
In the cases where one argument of min
is N
, Isabelle s simplifier (a term rewriting tactic, so to say), can solve the goal automatically. This is because the primcorec
command produces a bunch of lemmas, one of which states n = N m = N min n m = N
.
In the other case, we need to help Isabelle a bit to reduce the call to min (S n) (S m)
using the unfolding
methods, where min.code
contains exactly the equation that we used to specify min
. Using just unfolding min.code
would send this method into a loop, so we restrict it to the concrete arguments n
and m
. Then auto
can solve the remaining goal (despite all the existential quantifiers).
min.code
into the simpset, so working with objects that do not evaluate easily or completely is less strange.
module ENat where
open import Coinduction
data ENat : Set where
N : ENat
S : ENat ENat
min : ENat ENat ENat
min (S n') (S m') = S ( (min ( n') ( m')))
min _ _ = N
data le : ENat ENat Set where
leN : m le N m
leS : n m (le ( n) ( m)) le (S n) (S m)
min_le : n m le (min n m) n
min_le S n' S m' = leS ( min_le)
min_le N S m' = leN
min_le S n' N = leN
min_le N N = leN
I will refrain from commenting it, because I do not really know what I have been doing here, but it typechecks, and refer you to the official documentation on coinduction in Agda. But let me note that I wrote this using plain inductive types and recursion, and added
,
and
until it worked.
Two bodies traveling the world
commit 054c811b494746ec7304c3d495675046727ab114
Author: Chris Smith <cdsmith@gmail.com>
Date: Wed Jun 21 23:53:53 2017 +0000
Change dilated to take one parameter.
Function is nearly unused, so I'm not concerned about breakage.
This new version better aligns with standard educational usage,
in which "dilation" means uniform scaling. Taken as a separate
operation, it commutes with rotation, and preserves similarity
of shapes, neither of which is true of scaling in general.
Ok, that was quick to fix, and the CodeWorld server started to compile my code, and compiled, and aborted. It turned out that my program, presumably the larges CodeWorld interaction out there, hit the time limit of the compiler.
Luckily, Chris Smith just arrived at the venue, and he emergency-bumped the compiler time limit. The program compiled and I could start my presentation.
Unfortunately, the biggest blunder was still awaiting for me. I came to the slide where two instances of pong are played over a simulated network, and my point was that the two instances are perfectly in sync. Unfortunately, they were not. I guess it did support my point that lock-step simulation can easily go wrong, but it really left me out in the rain there, and I could not explain it I did not modify this code since New York, and there it worked flawless2. In the end, I could save my face a bit by running the real pong game against an attendee over the network, and no desynchronisation could be observed there.
Today I dug into it and it took me a while, and it turned out that the problem was not in CodeWorld, or the lock-step simulation code discussed in our paper about it, but in the code in my presentation that simulated the delayed network messages; in some instances it would deliver the UI events in different order to the two simulated players, and hence cause them do something different. Phew.
Monti, a better model than me
The last picture with the hat
-# RULES "foldr/nil" forall k z. foldr k z [] = z #-
taken right out of the standard library.
In my blog post I went through the algebraic laws that a small library of mine, successors, should fulfill, and sure enough, once I got to more interesting proofs, they would not go through just like that. At that point I had to add additional rules to the file I was editing, which helped the compiler to finish the proofs. Some of these rules were simple like
-# RULES "mapFB/id" forall c. mapFB c (\x -> x) = c #-
-# RULES "foldr/nil" forall k n. GHC.Base.foldr k n [] = n #-
-# RULES "foldr/undo" forall xs. GHC.Base.foldr (:) [] xs = xs #-
and some are more intricate like
-# RULES "foldr/mapFB" forall c f g n1 n2 xs.
GHC.Base.foldr (mapFB c f) n1 (GHC.Base.foldr (mapFB (:) g) n2 xs)
= GHC.Base.foldr (mapFB c (f.g)) (GHC.Base.foldr (mapFB c f) n1 n2) xs
#-
But there is something fishy going on here: The foldr/nil
rule is identical to a rule in the standard library! I should not have to add to my file that as I am proving things. So I knew that the GHC plugin, which I wrote to do these proofs, was doing something wrong, but I did not investigate for a while.
I returned to this problem recetly, and with the efficient and quick help of Simon Peyton Jones, I learned what I was doing wrong.1 After fixing it, I could remove all the simple rules from the files with my proofs. And to my surprise, I could remove the intricate rule as well!
So with this bug fixed, ghc-proofs is able to prove all the Functor, Applicative and Monad rules of the Succs functor without any additional rewrite rules, as you can see in the example file! (I still have to strategically place seq
s in a few places.)
That s great, isn t it! Yeah, sure. But having to introduce the rules at that point provided a very good narrative in my talk, so when I will give a similar talk next week in Pairs (actually, twice, first at the university and then at the Paris Haskell meetup, I will have to come up with a different example that calls for additional rewrite rules.
In related news: Since the last blog post, ghc-proofs learned to interpret proof specifications like
applicative_law4 :: Succs (a -> b) -> a -> Proof
applicative_law4 u y = u <*> pure y
=== pure ($ y) <*> u
where it previously only understood
applicative_law4 = (\ u (y::a) -> u <*> (pure y :: Succs a))
=== (\ u (y::a) -> pure ($ y) <*> u)
I am not sur if this should be uploaded to Hackage, but I invite you to play around with the GitHub version of ghc-proofs.
./boot.sh some-directory
, you will find that you can use the program some-directory/bin/veggies
just like like you would use ghc
. It comes with the full base
library, so your favorite variant of HelloWorld might just compile and run.
As of now, the code generation handles all the Core constructs (which is easy when you simply ignore all the types). It supports a good number of primitive operations, including pointers and arrays I implement these as need and has support for FFI calls into C.
ghc
-like experience. (I copied GHC s Main.hs
and inserted a few hooks, an approach I copied from GHCJS).Communication variants
Control.Applicative.Succs
, a new applicative functor. And because I am trying to keep my Haskell karma good1, I wanted to actually prove that my code fulfills the Applicative
and Monad
laws.
This led me to inserted writing long comments into my code, filled with lines like this:
The second Applicative law:
pure (.) <*> Succs u us <*> Succs v vs <*> Succs w ws
= Succs (.) [] <*> Succs u us <*> Succs v vs <*> Succs w ws
= Succs (u .) (map (.) us) <*> Succs v vs <*> Succs w ws
= Succs (u . v) (map ($v) (map (.) us) ++ map (u .) vs) <*> Succs w ws
= Succs (u . v) (map (($v).(.)) us ++ map (u .) vs) <*> Succs w ws
= Succs ((u . v) w) (map ($w) (map (($v).(.)) us ++ map (u .) vs) ++ map (u.v) ws)
= Succs ((u . v) w) (map (($w).($v).(.)) us ++ map (($w).(u.)) vs ++ map (u.v) ws)
= Succs (u (v w)) (map (\u -> u (v w)) us ++ map (\v -> u (v w)) vs ++ map (\w -> u (v w)) ws)
= Succs (u (v w)) (map ($(v w)) us ++ map u (map ($w) vs ++ map v ws))
= Succs u us <*> Succs (v w) (map ($w) vs ++ map v ws)
= Succs u us <*> (Succs v vs <*> Succs w ws)
Honk if you have done something like this before!
I proved all the laws, but I was very unhappy. I have a PhD on something about Haskell and theorem proving. I have worked with Isabelle, Agda and Coq. Both Haskell and theorem proving is decades old. And yet, I sit here, and tediously write manual proofs by hand. Is this really the best we can do?
Of course I could have taken my code, rewritten it in, say, Agda, and proved it correct there. But (right now) I don t care about Agda code. I care about my Haskell code! I don t want to write it twice, worry about copying mistakes and mismatchs in semantics, and have external proofs to maintain. Instead, I want to prove where I code, and have the proofs checked together with my code!
Then it dawned to me that this is, to some extent, possible. The Haskell compiler comes with a sophisticated program transformation machinery, which is meant to simplify and optimize code. But it can also be used to prove Haskell expressions to be equivalent! The idea is simple: Take two expressions, run both through the compiler s simplifier, and check if the results are the same. If they are, then the expressions are, as far as the compiler is concerned, equivalent.
A handful of hours later, I was able to write proof tasks like
app_law_2 = (\ a b (c::Succs a) -> pure (.) <*> a <*> b <*> c)
=== (\ a b c -> a <*> (b <*> c))
and others into my source file, and the compiler would tell me happily:
[1 of 1] Compiling Successors ( Successors.hs, Successors.o )
GHC.Proof: Proving getCurrent_proof1
GHC.Proof: Proving getCurrent_proof2
GHC.Proof: Proving getCurrent_proof3
GHC.Proof: Proving ap_star
GHC.Proof: Proving getSuccs_proof1
GHC.Proof: Proving getSuccs_proof2
GHC.Proof: Proving getSuccs_proof3
GHC.Proof: Proving app_law_1
GHC.Proof: Proving app_law_2
GHC.Proof: Proving app_law_3
GHC.Proof: Proving app_law_4
GHC.Proof: Proving monad_law_1
GHC.Proof: Proving monad_law_2
GHC.Proof: Proving monad_law_3
GHC.Proof: Proving return_pure
GHC.Proof proved 15 equalities
This is how I want to prove stuff about my code!
Do you also want to prove stuff about your code? I packaged this up as a GHC plugin in the Haskell library ghc-proofs
(not yet on Hackage). The README of the repository has a bit more detail on how to use this plugin, how it works, what its limitations are and where this is heading.
This is still only a small step, but finally there is a step towards low threshold program equivalence proofs in Haskell.
context fixes progName in
foo :: Maybe Int -> Either String Int
foo Nothing = Left $ progName ++ ": no number given"
foo (Just i) = bar i
bar :: Int -> Either String Int
bar 0 = Left $ progName ++ ": zero no good"
bar n = Right $ n + 1
instead of
foo :: String -> Maybe Int -> Either String Int
foo progName Nothing = Left $ progName ++ ": no number given"
foo progName (Just i) = bar progName i
bar :: String -> Int -> Either String Int
bar progName 0 = Left $ progName ++ ": zero no good"
bar progName n = Right $ n + 1
when you want to have an almost constant parameter.
I am happy to get feedback at the GitHub pull request.
/system/framework/framework.jar
file, put it into the input
folder of this project, run python main.py
, select 2
, and copy the framework.jar
from output/
back. Great.
foo.odex
in the Android system folder, runs some tool on them to create a classes.dex
file, and adds that to the corresponding foo.jar
or foo.apk
file, copies this back to the phone and deletes the foo.odex
file.
The annoying this is that one does not only have to do it for framework.jar
in order to please tingle, because if one does it to one odex
file, one has to do to all! It seems that for people using Windows, the Universal Deodexer V5 seems to be a convenient tool, but I had to go more manually.
So I first fetched smali , compiled it using ./gradlew build
. Then I fetched the folders /opt/alien/system/framework
and /opt/alien/system/app
from the phone (e.g. using scp). Keep a backup of these in case something breaks. Then I ran these commands (disclaimer: I fetched these from my bash history and slightly cleaned them up. This is not a fire-and-forget script! Use it when you know what it and you are doing):
cd framework
for file in *.odex
do
java -jar ~/build/smali/baksmali/build/libs/baksmali.jar deodex $file -o out
java -jar ~/build/smali/smali/build/libs/smali.jar a out -o classes.dex
zip -u $(basename $file .odex).jar classes.dex
rm -rf out classes.dex $file
done
cd ..
cd app
for file in *.odex
do
java -jar ~/build/smali/baksmali/build/libs/baksmali.jar deodex -d ../framework $file -o out
java -jar ~/build/smali/smali/build/libs/smali.jar a out -o classes.dex
zip -u $(basename $file .odex).apk classes.dex
rm -rf out classes.dex $file
done
cd ..
The resulting framework.jar
can now be patched with tingle:
mv framework/framework.jar ~/build/tingle/input
cd ~/build/tingle
./main.py
# select 2
cd -
mv ~/build/tingle/output/framework.jar framework/framework.jar
Now I copy these framework
and app
folders back on my phone, and restart Dalvik:
devel-su systemctl restart aliendalvik.service
It might start a bit slower than usually, but eventually, all the Android apps should work as before.
The final bit that was missing in my case was that I had to reinstall Signal: If it is installed before microG
is installed, it does not get permission to use GCM, and when it tries (while registering: After generating the keys) it just crashes. I copied /data/data/org.thoughtcrime.secretsms/
before removing Signal and moved it back after (with cp -a
to preserve permissions) so that I could keep my history.
And now, it seems, vanilla Signal is working just fine on my Jolla phone!
Functor
, Applicative
and Monad
, and to highlight the value of the Applicative
abstraction.
I quite like the example that I came up with, so I want to share it here. In the interest of long-term archival and stand-alone presentation, I include all the material in this post.1
import Data.Char
import Data.Maybe
import Data.List
import System.Environment
import System.IO
import System.Exit
newtype Parser a = P (String -> Maybe (a, String))
runParser :: Parser t -> String -> Maybe (t, String)
runParser (P p) = p
parse :: Parser a -> String -> Maybe a
parse p input = case runParser p input of
Just (result, "") -> Just result
_ -> Nothing -- handles both no result and leftover input
noParserP :: Parser a
noParserP = P (\_ -> Nothing)
pureParserP :: a -> Parser a
pureParserP x = P (\input -> Just (x,input))
instance Functor Parser where
fmap f p = P $ \input -> do
(x, rest) <- runParser p input
return (f x, rest)
instance Applicative Parser where
pure = pureParserP
p1 <*> p2 = P $ \input -> do
(f, rest1) <- runParser p1 input
(x, rest2) <- runParser p2 rest1
return (f x, rest2)
instance Monad Parser where
return = pure
p1 >>= k = P $ \input -> do
(x, rest1) <- runParser p1 input
runParser (k x) rest1
anyCharP :: Parser Char
anyCharP = P $ \input -> case input of
(c:rest) -> Just (c, rest)
[] -> Nothing
charP :: Char -> Parser ()
charP c = do
c' <- anyCharP
if c == c' then return ()
else noParserP
anyCharButP :: Char -> Parser Char
anyCharButP c = do
c' <- anyCharP
if c /= c' then return c'
else noParserP
letterOrDigitP :: Parser Char
letterOrDigitP = do
c <- anyCharP
if isAlphaNum c then return c else noParserP
orElseP :: Parser a -> Parser a -> Parser a
orElseP p1 p2 = P $ \input -> case runParser p1 input of
Just r -> Just r
Nothing -> runParser p2 input
manyP :: Parser a -> Parser [a]
manyP p = (pure (:) <*> p <*> manyP p) orElseP pure []
many1P :: Parser a -> Parser [a]
many1P p = pure (:) <*> p <*> manyP p
sepByP :: Parser a -> Parser () -> Parser [a]
sepByP p1 p2 = (pure (:) <*> p1 <*> (manyP (p2 *> p1))) orElseP pure []
A parser using this library for, for example, CSV files could take this form:
parseCSVP :: Parser [[String]]
parseCSVP = manyP parseLine
where
parseLine = parseCell sepByP charP ',' <* charP '\n'
parseCell = do
charP '"'
content <- manyP (anyCharButP '"')
charP '"'
return content
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
It is straightforward to create a Haskell data type to represent an EBNF syntax description. Here is a simple EBNF library (data type and pretty-printer) for your convenience:
data RHS
= Terminal String
NonTerminal String
Choice RHS RHS
Sequence RHS RHS
Optional RHS
Repetition RHS
deriving (Show, Eq)
ppRHS :: RHS -> String
ppRHS = go 0
where
go _ (Terminal s) = surround "'" "'" $ concatMap quote s
go _ (NonTerminal s) = s
go a (Choice x1 x2) = p a 1 $ go 1 x1 ++ " " ++ go 1 x2
go a (Sequence x1 x2) = p a 2 $ go 2 x1 ++ ", " ++ go 2 x2
go _ (Optional x) = surround "[" "]" $ go 0 x
go _ (Repetition x) = surround " " " " $ go 0 x
surround c1 c2 x = c1 ++ x ++ c2
p a n a > n = surround "(" ")"
otherwise = id
quote '\'' = "\\'"
quote '\\' = "\\\\"
quote c = [c]
type Production = (String, RHS)
type BNF = [Production]
ppBNF :: BNF -> String
ppBNF = unlines . map (\(i,rhs) -> i ++ " = " ++ ppRHS rhs ++ ";")
RHS
type directly, but we can do something more nifty: We create a data type that keeps track, via a phantom type parameter, of what Haskell type the given EBNF syntax is the specification:
newtype Grammar a = G RHS
ppGrammar :: Grammar a -> String
ppGrammar (G rhs) = ppRHS rhs
So a value of type Grammar t
is a description of the textual representation of the Haskell type t
.
Here is one simple example:
anyCharG :: Grammar Char
anyCharG = G (NonTerminal "char")
Here is another one. This one does not describe any interesting Haskell type, but is useful when spelling out the special characters in the syntax described by the grammar:
charG :: Char -> Grammar ()
charG c = G (Terminal [c])
A combinator that creates new grammar from two existing grammars:
orElseG :: Grammar a -> Grammar a -> Grammar a
orElseG (G rhs1) (G rhs2) = G (Choice rhs1 rhs2)
We want the convenience of our well-known type classes in order to combine these values some more:
instance Functor Grammar where
fmap _ (G rhs) = G rhs
instance Applicative Grammar where
pure x = G (Terminal "")
(G rhs1) <*> (G rhs2) = G (Sequence rhs1 rhs2)
Note how the Functor
instance does not actually use the function. How should it? There are no values inside a Grammar
!
We cannot define a Monad
instance for Grammar
: We would start with (G rhs1) >>= k =
, but there is simply no way of getting a value of type a
that we can feed to k
. So we will do without a Monad
instance. This is interesting, and we will come back to that later.
Like with the parser, we can now begin to build on the primitive example to build more complicated combinators:
manyG :: Grammar a -> Grammar [a]
manyG p = (pure (:) <*> p <*> manyG p) orElseG pure []
many1G :: Grammar a -> Grammar [a]
many1G p = pure (:) <*> p <*> manyG p
sepByG :: Grammar a -> Grammar () -> Grammar [a]
sepByG p1 p2 = ((:) <$> p1 <*> (manyG (p2 *> p1))) orElseG pure []
Let us run a small example:
dottedWordsG :: Grammar [String]
dottedWordsG = many1G (manyG anyCharG <* charG '.')
*Main> putStrLn $ ppGrammar dottedWordsG
'', ('', char, ('', char, ('', char, ('', char, ('', char, ('',
Oh my, that is not good. Looks like the recursion in manyG
does not work well, so we need to avoid that. But anyways we want to be explicit in the EBNF grammars about where something can be repeated, so let us just make many
a primitive:
manyG :: Grammar a -> Grammar [a]
manyG (G rhs) = G (Repetition rhs)
With this definition, we already get a simple grammar for dottedWordsG
:
*Main> putStrLn $ ppGrammar dottedWordsG
'', char , '.', char , '.'
This already looks like a proper EBNF grammar. One thing that is not nice about it is that there is an empty string (''
) in a sequence ( ,
). We do not want that.
Why is it there in the first place? Because our Applicative
instance is not lawful! Remember that pure id <*> g == g
should hold. One way to achieve that is to improve the Applicative
instance to optimize this case away:
instance Applicative Grammar where
pure x = G (Terminal "")
G (Terminal "") <*> G rhs2 = G rhs2
G rhs1 <*> G (Terminal "") = G rhs1
(G rhs1) <*> (G rhs2) = G (Sequence rhs1 rhs2)
Now we get what we want:
*Main> putStrLn $ ppGrammar dottedWordsG
char , '.', char , '.'
Remember our parser for CSV files above? Let me repeat it here, this time using only Applicative
combinators, i.e. avoiding (>>=)
, (>>)
, return
and do
-notation:
parseCSVP :: Parser [[String]]
parseCSVP = manyP parseLine
where
parseLine = parseCell sepByP charG ',' <* charP '\n'
parseCell = charP '"' *> manyP (anyCharButP '"') <* charP '"'
And now we try to rewrite the code to produce Grammar
instead of Parser
. This is straightforward with the exception of anyCharButP
. The parser code for that inherently monadic, and we just do not have a monad instance. So we work around the issue by making that a primitive grammar, i.e. introducing a non-terminal in the EBNF without a production rule pretty much like we did for anyCharG
:
primitiveG :: String -> Grammar a
primitiveG s = G (NonTerminal s)
parseCSVG :: Grammar [[String]]
parseCSVG = manyG parseLine
where
parseLine = parseCell sepByG charG ',' <* charG '\n'
parseCell = charG '"' *> manyG (primitiveG "not-quote") <* charG '"'
Of course the names parse
are not quite right any more, but let us just leave that for now.
Here is the result:
*Main> putStrLn $ ppGrammar parseCSVG
('"', not-quote , '"', ',', '"', not-quote , '"' ''), '
'
The line break is weird. We do not really want newlines in the grammar. So let us make that primitive as well, and replace charG '\n'
with newlineG
:
newlineG :: Grammar ()
newlineG = primitiveG "newline"
Now we get
*Main> putStrLn $ ppGrammar parseCSVG
('"', not-quote , '"', ',', '"', not-quote , '"' ''), newline
which is nice and correct, but still not quite the easily readable EBNF that we saw further up.
newtype Grammar a = G (BNF, RHS)
runGrammer :: String -> Grammar a -> BNF
runGrammer main (G (prods, rhs)) = prods ++ [(main, rhs)]
ppGrammar :: String -> Grammar a -> String
ppGrammar main g = ppBNF $ runGrammer main g
Now we have to adjust all our primitive combinators (but not the derived ones!):
charG :: Char -> Grammar ()
charG c = G ([], Terminal [c])
anyCharG :: Grammar Char
anyCharG = G ([], NonTerminal "char")
manyG :: Grammar a -> Grammar [a]
manyG (G (prods, rhs)) = G (prods, Repetition rhs)
mergeProds :: [Production] -> [Production] -> [Production]
mergeProds prods1 prods2 = nub $ prods1 ++ prods2
orElseG :: Grammar a -> Grammar a -> Grammar a
orElseG (G (prods1, rhs1)) (G (prods2, rhs2))
= G (mergeProds prods1 prods2, Choice rhs1 rhs2)
instance Functor Grammar where
fmap _ (G bnf) = G bnf
instance Applicative Grammar where
pure x = G ([], Terminal "")
G (prods1, Terminal "") <*> G (prods2, rhs2)
= G (mergeProds prods1 prods2, rhs2)
G (prods1, rhs1) <*> G (prods2, Terminal "")
= G (mergeProds prods1 prods2, rhs1)
G (prods1, rhs1) <*> G (prods2, rhs2)
= G (mergeProds prods1 prods2, Sequence rhs1 rhs2)
primitiveG :: String -> Grammar a
primitiveG s = G (NonTerminal s)
The use of nub
when combining productions removes duplicates that might be used in different parts of the grammar. Not efficient, but good enough for now.
Did we gain anything? Not yet:
*Main> putStr $ ppGrammar "csv" (parseCSVG)
csv = ('"', not-quote , '"', ',', '"', not-quote , '"' ''), newline ;
But we can now introduce a function that lets us tell the system where to give names to a piece of grammar:
nonTerminal :: String -> Grammar a -> Grammar a
nonTerminal name (G (prods, rhs))
= G (prods ++ [(name, rhs)], NonTerminal name)
Ample use of this in parseCSVG
yields the desired result:
parseCSVG :: Grammar [[String]]
parseCSVG = manyG parseLine
where
parseLine = nonTerminal "line" $
parseCell sepByG charG ',' <* newline
parseCell = nonTerminal "cell" $
charG '"' *> manyG (primitiveG "not-quote") <* charG '"
*Main> putStr $ ppGrammar "csv" (parseCSVG)
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
This is great!
parseCSVG
and parseCSVP
are! Would it not be great if we could implement that functionality only once, and get both a parser and a grammar description out of it? This way, the two would never be out of sync!
And surely this must be possible. The tool to reach for is of course to define a type class that abstracts over the parts where Parser
and Grammer
differ. So we have to identify all functions that are primitive in one of the two worlds, and turn them into type class methods. This includes char
and orElse
. It includes many
, too: Although manyP
is not primitive, manyG
is. It also includes nonTerminal
, which does not exist in the world of parsers (yet), but we need it for the grammars.
The primitiveG
function is tricky. We use it in grammars when the code that we might use while parsing is not expressible as a grammar. So the solution is to let it take two arguments: A String
, when used as a descriptive non-terminal in a grammar, and a Parser a
, used in the parsing code.
Finally, the type classes that we except, Applicative
(and thus Functor
), are added as constraints on our type class:
class Applicative f => Descr f where
char :: Char -> f ()
many :: f a -> f [a]
orElse :: f a -> f a -> f a
primitive :: String -> Parser a -> f a
nonTerminal :: String -> f a -> f a
The instances are easily written:
instance Descr Parser where
char = charP
many = manyP
orElse = orElseP
primitive _ p = p
nonTerminal _ p = p
instance Descr Grammar where
char = charG
many = manyG
orElse = orElseG
primitive s _ = primitiveG s
nonTerminal s g = nonTerminal s g
And we can now take the derived definitions, of which so far we had two copies, and define them once and for all:
many1 :: Descr f => f a -> f [a]
many1 p = pure (:) <*> p <*> many p
anyChar :: Descr f => f Char
anyChar = primitive "char" anyCharP
dottedWords :: Descr f => f [String]
dottedWords = many1 (many anyChar <* char '.')
sepBy :: Descr f => f a -> f () -> f [a]
sepBy p1 p2 = ((:) <$> p1 <*> (many (p2 *> p1))) orElse pure []
newline :: Descr f => f ()
newline = primitive "newline" (charP '\n')
And thus we now have our CSV parser/grammar generator:
parseCSV :: Descr f => f [[String]]
parseCSV = many parseLine
where
parseLine = nonTerminal "line" $
parseCell sepBy char ',' <* newline
parseCell = nonTerminal "cell" $
char '"' *> many (primitive "not-quote" (anyCharButP '"')) <* char '"'
We can now use this definition both to parse and to generate grammars:
*Main> putStr $ ppGrammar2 "csv" (parseCSV)
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
*Main> parse parseCSV "\"ab\",\"cd\"\n\"\",\"de\"\n\n"
Just [["ab","cd"],["","de"],[]]
parseINIP :: Parser INIFile
parseINIP = many1P parseSection
where
parseSection =
(,) <$ charP '['
<*> parseIdent
<* charP ']'
<* charP '\n'
<*> (catMaybes <$> manyP parseLine)
parseIdent = many1P letterOrDigitP
parseLine = parseDecl orElseP parseComment orElseP parseEmpty
parseDecl = Just <$> (
(,) <*> parseIdent
<* manyP (charP ' ')
<* charP '='
<* manyP (charP ' ')
<*> many1P (anyCharButP '\n')
<* charP '\n')
parseComment =
Nothing <$ charP '#'
<* many1P (anyCharButP '\n')
<* charP '\n'
parseEmpty = Nothing <$ charP '\n'
Transforming that to a generic description is quite straightforward. We use primitive
again to wrap letterOrDigitP
:
descrINI :: Descr f => f INIFile
descrINI = many1 parseSection
where
parseSection =
(,) <* char '['
<*> parseIdent
<* char ']'
<* newline
<*> (catMaybes <$> many parseLine)
parseIdent = many1 (primitive "alphanum" letterOrDigitP)
parseLine = parseDecl orElse parseComment orElse parseEmpty
parseDecl = Just <$> (
(,) <*> parseIdent
<* many (char ' ')
<* char '='
<* many (char ' ')
<*> many1 (primitive "non-newline" (anyCharButP '\n'))
<* newline)
parseComment =
Nothing <$ char '#'
<* many1 (primitive "non-newline" (anyCharButP '\n'))
<* newline
parseEmpty = Nothing <$ newline
This yields this not very helpful grammar (abbreviated here):
*Main> putStr $ ppGrammar2 "ini" descrINI
ini = '[', alphanum, alphanum , ']', newline, alphanum, alphanum , ' '
But with a few uses of nonTerminal
, we get something really nice:
descrINI :: Descr f => f INIFile
descrINI = many1 parseSection
where
parseSection = nonTerminal "section" $
(,) <$ char '['
<*> parseIdent
<* char ']'
<* newline
<*> (catMaybes <$> many parseLine)
parseIdent = nonTerminal "identifier" $
many1 (primitive "alphanum" letterOrDigitP)
parseLine = nonTerminal "line" $
parseDecl orElse parseComment orElse parseEmpty
parseDecl = nonTerminal "declaration" $ Just <$> (
(,) <$> parseIdent
<* spaces
<* char '='
<* spaces
<*> remainder)
parseComment = nonTerminal "comment" $
Nothing <$ char '#' <* remainder
remainder = nonTerminal "line-remainder" $
many1 (primitive "non-newline" (anyCharButP '\n')) <* newline
parseEmpty = Nothing <$ newline
spaces = nonTerminal "spaces" $ many (char ' ')
*Main> putStr $ ppGrammar "ini" descrINI
identifier = alphanum, alphanum ;
spaces = ' ' ;
line-remainder = non-newline, non-newline , newline;
declaration = identifier, spaces, '=', spaces, line-remainder;
comment = '#', line-remainder;
line = declaration comment newline;
section = '[', identifier, ']', newline, line ;
ini = section, section ;
const = digit, digit ;
spaces = ' ' newline ;
atom = const '(', spaces, expr, spaces, ')', spaces;
mult = atom, spaces, '*', spaces, atom , spaces;
plus = mult, spaces, '+', spaces, mult , spaces;
expr = plus;
The production of expr
is recursive (via plus
, mult
, atom
). We have seen above that simply defining a Grammar a
recursively does not go well.
One solution is to add a new combinator for explicit recursion, which replaces nonTerminal
in the method:
class Applicative f => Descr f where
recNonTerminal :: String -> (f a -> f a) -> f a
instance Descr Parser where
recNonTerminal _ p = let r = p r in r
instance Descr Grammar where
recNonTerminal = recNonTerminalG
recNonTerminalG :: String -> (Grammar a -> Grammar a) -> Grammar a
recNonTerminalG name f =
let G (prods, rhs) = f (G ([], NonTerminal name))
in G (prods ++ [(name, rhs)], NonTerminal name)
nonTerminal :: Descr f => String -> f a -> f a
nonTerminal name p = recNonTerminal name (const p)
runGrammer :: String -> Grammar a -> BNF
runGrammer main (G (prods, NonTerminal nt)) main == nt = prods
runGrammer main (G (prods, rhs)) = prods ++ [(main, rhs)]
The change in runGrammer
avoids adding a pointless expr = expr
production to the output.
This lets us define a parser/grammar-generator for the arithmetic expressions given above:
data Expr = Plus Expr Expr Mult Expr Expr Const Integer
deriving Show
mkPlus :: Expr -> [Expr] -> Expr
mkPlus = foldl Plus
mkMult :: Expr -> [Expr] -> Expr
mkMult = foldl Mult
parseExpr :: Descr f => f Expr
parseExpr = recNonTerminal "expr" $ \ exp ->
ePlus exp
ePlus :: Descr f => f Expr -> f Expr
ePlus exp = nonTerminal "plus" $
mkPlus <$> eMult exp
<*> many (spaces *> char '+' *> spaces *> eMult exp)
<* spaces
eMult :: Descr f => f Expr -> f Expr
eMult exp = nonTerminal "mult" $
mkPlus <$> eAtom exp
<*> many (spaces *> char '*' *> spaces *> eAtom exp)
<* spaces
eAtom :: Descr f => f Expr -> f Expr
eAtom exp = nonTerminal "atom" $
aConst orElse eParens exp
aConst :: Descr f => f Expr
aConst = nonTerminal "const" $ Const . read <$> many1 digit
eParens :: Descr f => f a -> f a
eParens inner =
id <$ char '('
<* spaces
<*> inner
<* spaces
<* char ')'
<* spaces
And indeed, this works:
*Main> putStr $ ppGrammar "expr" parseExpr
const = digit, digit ;
spaces = ' ' newline ;
atom = const '(', spaces, expr, spaces, ')', spaces;
mult = atom, spaces, '*', spaces, atom , spaces;
plus = mult, spaces, '+', spaces, mult , spaces;
expr = plus;
recNonTerminal
and explicitly passing around the recursive call (i.e. the exp
in the example). To implement that we have to adjust our Grammar
type as follows:
newtype Grammar a = G ([String] -> (BNF, RHS))
The idea is that the list of strings is those non-terminals that we are currently defining. So in nonTerminal
, we check if the non-terminal to be introduced is currently in the process of being defined, and then simply ignore the body. This way, the recursion is stopped automatically:
nonTerminalG :: String -> (Grammar a) -> Grammar a
nonTerminalG name (G g) = G $ \seen ->
if name elem seen
then ([], NonTerminal name)
else let (prods, rhs) = g (name : seen)
in (prods ++ [(name, rhs)], NonTerminal name)
After adjusting the other primitives of Grammar
(including the Functor
and Applicative
instances, wich now again have nonTerminal
) to type-check again, we observe that this parser/grammar generator for expressions, with genuine recursion, works now:
parseExp :: Descr f => f Expr
parseExp = nonTerminal "expr" $
ePlus
ePlus :: Descr f => f Expr
ePlus = nonTerminal "plus" $
mkPlus <$> eMult
<*> many (spaces *> char '+' *> spaces *> eMult)
<* spaces
eMult :: Descr f => f Expr
eMult = nonTerminal "mult" $
mkPlus <$> eAtom
<*> many (spaces *> char '*' *> spaces *> eAtom)
<* spaces
eAtom :: Descr f => f Expr
eAtom = nonTerminal "atom" $
aConst orElse eParens parseExp
Note that the recursion is only going to work if there is at least one call to nonTerminal
somewhere around the recursive calls. We still cannot implement many
as naively as above.
identifier = letter, letter digit '-' ;
spaces = ' ' newline ;
quoted-char = non-quote-or-backslash '\\', '\\' '\\', '\'';
terminal = '\'', quoted-char , '\'', spaces;
non-terminal = identifier, spaces;
option = '[', spaces, rhs, spaces, ']', spaces;
repetition = ' ', spaces, rhs, spaces, ' ', spaces;
group = '(', spaces, rhs, spaces, ')', spaces;
atom = terminal non-terminal option repetition group;
sequence = atom, spaces, ',', spaces, atom , spaces;
choice = sequence, spaces, ' ', spaces, sequence , spaces;
rhs = choice;
production = identifier, spaces, '=', spaces, rhs, ';', spaces;
bnf = production, production ;
This grammar is set up so that the precedence of ,
and
is correctly implemented: a , b c
will parse as (a, b) c
.
In this syntax for BNF, terminal characters are quoted, i.e. inside ' '
, a '
is replaced by \'
and a \
is replaced by \\
this is done by the function quote
in ppRHS
.
If you do this, you should able to round-trip with the pretty-printer, i.e. parse back what it wrote:
*Main> let bnf1 = runGrammer "expr" parseExpr
*Main> let bnf2 = runGrammer "expr" parseBNF
*Main> let f = Data.Maybe.fromJust . parse parseBNF. ppBNF
*Main> f bnf1 == bnf1
True
*Main> f bnf2 == bnf2
True
The last line is quite meta: We are using parseBNF
as a parser on the pretty-printed grammar produced from interpreting parseBNF
as a grammar.
Applicative
, and not Monad
. Applicative
is less powerful, so by giving less power to the user of our Descr
interface, the other side, i.e. the implementation, can be more powerful.
The reason why Applicative
is ok, but Monad
is not, is that in Applicative
, the results do not affect the shape of the computation, whereas in Monad
, the whole point of the bind operator (>>=)
is that the result of the computation is used to decide the next computation. And while this is perfectly fine for a parser, it just makes no sense for a grammar generator, where there simply are no values around!
We have also seen that a phantom type, namely the parameter of Grammar
, can be useful, as it lets the type system make sure we do not write nonsense. For example, the type of orElseG
ensures that both grammars that are combined here indeed describe something of the same type.
Applicative
yesterday. <*>
and <*
the >
point out where the arguments are that are being passed to the function on the left. FONTFACE="Terminus"
FONTSIZE="12x24"
in /etc/default/console-setup
yielded good results.gtk-font-name="Sans 16"
in ~/.gtkrc-2.0
. Similarly, for GTK-3 I have
[Settings]
gtk-font-name = Sans 16
in ~/.config/gtk-3.0/settings.ini
.gnome-terminal
, Evolution and hexchat refer to the System default document font and System default monospace font . gsettings set org.gnome.desktop.interface monospace-font-name 'Monospace 16'
gsettings set org.gnome.desktop.interface document-font-name 'Serif 16'
gsettings set org.gnome.desktop.interface font-name 'Sans 16'
layout.css.devPixelsPerPx
to 1.5
in about:config
.set guifont=Monospace\ 16
in ~/.vimrc
. The toolbar is tiny, but I hardly use it anyways., font = "xft:Sans:size=16"
Speaking about Xmonad prompts: Check out the XMonad.Prompt.Unicode
module that I have been using for years and recently submitted upstream.--force-device-scale-factor=1.5
.xrandr --dpi 194
before hand. This seems also to be read by Firefox, doubling the effect of the font size in the gtk settings, which is annoying. Luckily I do not work with Libreoffice often, so for now I ll just set that manually when needed./etc/X11/xorg.conf.d/30-touchpad.conf
Section "InputClass"
Identifier "SynPS/2 Synaptics TouchPad"
MatchProduct "SynPS/2 Synaptics TouchPad"
Option "ignore" "on"
EndSection
At one point I left out the MatchProduct
line, disabling all input in the X server. Had to boot into recovery mode to fix that.
Unfortunately, there is something wrong with the trackpoint and the buttons: When I am moving the trackpoint (and maybe if there is actual load on the machine), mouse button press and release events sometimes get lost. This is quite annoying I try to open a folder in Evolution and accidentially move it.
I installed the latest Kernel from Debian experimental (4.8.0-rc8), but it did not help.
I filed a bug report against libinput
although I am not fully sure that that s the culprit.
Update: According to Benjamin Tissoires it is a known firmware bug and the appropriate people are working on a work-around. Until then I am advised to keep my palm of the touchpad.
Also, I found the trackpoint too slow. I am not sure if it is simply because of the large resolution of the screen, or because some movement events are also swallowed. For now, I simply changed the speed by writing
SUBSYSTEM=="serio", DRIVERS=="psmouse", ATTRS speed ="120"
to /etc/udev/rules.d/10-trackpoint.rules
.
Section "Device"
Identifier "card0"
Driver "intel"
Option "Backlight" "intel_backlight"
BusID "PCI:0:2:0"
EndSection
so that the command line tool xbacklight
would work, and then use Xmonad keybinds to perform the action, just as I already do for sound control:
, ((0, xF86XK_Sleep), spawn "dbus-send --system --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower.Suspend")
, ((0, xF86XK_AudioMute), spawn "ponymix toggle")
, ((0, 0x1008ffb2 - xF86XK_AudioMicMute - ), spawn "ponymix --source toggle")
, ((0, xF86XK_AudioRaiseVolume), spawn "ponymix increase 5")
, ((0, xF86XK_AudioLowerVolume), spawn "ponymix decrease 5")
, ((shiftMask, xF86XK_AudioRaiseVolume), spawn "ponymix increase 5 --max-volume 200")
, ((shiftMask, xF86XK_AudioLowerVolume), spawn "ponymix decrease 5")
, ((0, xF86XK_MonBrightnessUp), spawn "xbacklight +10")
, ((0, xF86XK_MonBrightnessDown), spawn "xbacklight -10")
The T460s does not actually have a sleep button, that line is a reminiscence from my T430s. I suspend the machine by pressing the power button now, thanks to HandlePowerKey=suspend
in /etc/systemd/logind.conf
.
/etc
look similar enough.
I use ~/.profile
to extend the PATH
and set some other variables. Previously, these settings were in effect in my whole X session, which is started by lightdm
with auto-login, followed by xmonad-session
. I could find no better way to fix that than stating . ~/.profile
early in my ~/.xmonad/xmonad-session-rc
. Very strange.
hindent
is released in a new version, and getting quite a bit of deserved attention. He is polling the Haskell programmers on whether two or four spaces are the right indentation. But that is just cosmetics
I am in principle very much in favor of automatic formatting, and I hope that a tool like hindent
will eventually be better at formatting code than a human.
But it currently is not there yet. Code is literature meant to be read, and good code goes at length to be easily readable, and formatting can carry semantic information.
The Haskell syntax was (at least I get that impression) designed to allow the authors to write nicely looking, easy to understand code. One important tool here is vertical alignment of corresponding concepts on different lines. Compare
maze :: Integer -> Integer -> Integer
maze x y
abs x > 4 abs y > 4 = 0
abs x == 4 abs y == 4 = 1
x == 2 && y <= 0 = 1
x == 3 && y <= 0 = 3
x >= -2 && y == 0 = 4
otherwise = 2
with
maze :: Integer -> Integer -> Integer
maze x y
abs x > 4 abs y > 4 = 0
abs x == 4 abs y == 4 = 1
x == 2 && y <= 0 = 1
x == 3 && y <= 0 = 3
x >= -2 && y == 0 = 4
otherwise = 2
The former is a quick to grasp specification, the latter (the output of hindent
at the moment) is a desert of numbers and operators.
I see two ways forward:
hindent
get improved to the point that they are able to detect such patterns, and indent it properly (which would be great, but very tricky, and probably never complete) or
on the same column, and keep them aligned.lhs2Tex
(which, IMHO, with careful input, a proportional font and with the great polytable
LaTeX backend, produces the most pleasing code listings) takes. There, two spaces or more indicate an alignment point, and if two such alignment points are in the same column, their alignment is preserved even if there are lines in between!
With the latter approach, the code up there would be written
maze :: Integer -> Integer -> Integer
maze x y
abs x > 4 abs y > 4 = 0
abs x == 4 abs y == 4 = 1
x == 2 && y <= 0 = 1
x == 3 && y <= 0 = 3
x >= -2 && y == 0 = 4
otherwise = 2
And now the intended alignment is explicit.1: 3.50000 (+3.50000)
2: 8.23611 (+4.73611)
3: 13.42490 (+5.18879)
4: 18.84364 (+5.41874)
5: 24.43605 (+5.59241)
6: 30.15198 (+5.71592)
7: 35.95216 (+5.80018)
8: 41.80969 (+5.85753)
9: 47.70676 (+5.89707)
Note that this calculation, although printed as floating point numbers, is performed using fractions of unbounded integers, so there are no rounding issues that could skew the result.
The result supported the hypothesis that there is no point in rolling a 6 again: The value of an additional die grows and approaches 6 from beyond, but judging from these number is never going to reach it.
Then again nothing happened. Until 14 month later, when some Byron Schmuland came along, found this an interesting puzzle, and set out a 500 point bounty to whoever solved this problem. This attracted a bit attention, and a few not very successful attempts at solving this. Eventually it reached twitter, where Roman Cheplyaka linked to it.
Coincidally a day later some joriki came along, and he had a very good idea: Why not make our life easier and think about dice with less sides, and look at 3 instead of 6. This way, and using a more efficient implementation (but still properly using rationals), he could do a similar calculation for up to 50 dice. And it was very lucky that he went to 50, and not just 25, because up to 27, the results were very much as expected, approaching value of +3 from below. But then it surpassed +3 and became +3.000000008463403.
In other words: If you have roll 28 dice, and you have exactly two dice showing a 3, then it gives you better expected score if you set aside only one 3, and not both of them. The advantage is minuscule, but that does not matter it is there.
From then on, the results behaved strangely. Between 28 and 34, the additional value was larger than 3. Then, from 35 on again lower than 2. It oscillated. Something similar could be observed when the game is played with coins.
Eventually, joriki improved his code and applied enough tricks so that he could solve it also for the 6-sided die: The difference of the expected value of 198 dice and having 199 dice is larger than 6 (by 10 21...)!
The optimizations that allowed him to calculate these numbers in a reasonable amount of time unfortunately was to assume that my original hypothesis (never rerolling a 6 is optimal), which held until n < 199. But this meant that for n > 199, the code did not yield correct results.
What is the rationale of the story? Don t trust common sense when it comes to statistics; don t judge a sequence just from a few initial numbers; if you have an interesting question, post it online and wait for 16 months.
Next.