Friendly Ghost

@vogti is writing.

3 Wochen Urlaub, bitches!


Closures: JavaScript vs. Elixir

Ein kleiner Vergleich zwischen JavaScript und Elixir Closures.

Nicht so coole Closures: JavaScript

Ich verwette meinen Hut darauf, dass die ersten Probleme derer die mit JavaScript neu beginnen, mit Closures zutun haben - ohne dass sie überhaupt von deren Existenz wissen. Spätestens bei dem Versuch über ein Array zu iterieren und eine Aktion basierend auf dem aktuellen Index des Elements auszuführen kommt man in die Bredouille.

var array = []

for (var i = 0; i < 3; i += 1) {  
  array.push(function() {
    console.log(i);
  });
}

array[0]()  
array[1]()  
array[2]()  

Sieht logisch aus - ähnlich würde man es wohl auch in anderen Sprachen machen. Okay, lassen wir das mal laufen:

$ node closure.js
3  
3  
3  

Oha. Was ist hier los?

JavaScripts Funktionen speichern die Umgebung in der sie definiert wurden. Somit haben wir innerhalb jeder Funktion zugriff auf das gleiche i. Setzt man in die condition ein console.log(i); wird relativ deutlich was passiert:

var array = []

for (var i = 0; i < 3; i += 1) {  
  console.log(i);
  array.push(function() {
    console.log(i);
  });
}

array[0]()  
array[1]()  
array[2]()  
node closures.js  
0  
1  
2  
3  
3  
3  

Der Wert von i wird vom ersten Funktionsaufruf (array[0]()) von 0 zu 1, von 1 zu 2 und anschließend von 2 zu 3 verändert. Von allen drei Funktionsaufrufen wird der letzte wert bei dem die condition anhält (i < 3, also 3) ausgegeben. Die Funktionsaufrufe array[1]() und array[2]() greifen also auf das gleiche, von array[0]() bereits hochgezählte i zu. In meinen augen ziemlich unintuitiv.

JS Closure not working

Aber wie löst der geneigte JavaScript Entwickler nun das Problem?

var array = []

for (var i=0; i<3; i+=1) {  
  (function(i) {
    array.push(function() {
      console.log(i);
    });
  })(i);
}

array[0]()  
array[1]()  
array[2]()  
$ node closure_fix.js
0  
1  
2  

Löppt! Wir haben einen neuen Kontext erzeugt indem wir die definition unserer anonymen Funktion in eine weitere anonyme Funktion verpackt haben. Anschließend wird die Funktion aufgerufen... Et voila!

JS Closure working Mozilla erklärt Closures wie folgt:

A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.

Source: Mozilla Developer Network: Closures

Meines Erachtens nach sind Anfänger dazu verdammt hierüber zu stolpern, da die Lösung dieses Problem zu umgehen fernab von intuitiv ist.

For the cool kids: Elixir

Das gleiche in Elixir:

closures = (0..2)  
  |> Enum.map &( fn () -> IO.puts(&1) end)

closures  
  |> Enum.each &(&1.())
$ elixir closure_pretty.ex
0  
1  
2  

Hier wird der Pipe-Operator (|>) genutzt. Dieser erlaubt es uns die Reihenfolge der Methodenaufrufe umzukehren, den vorherigen Ausdruck zu nehmen und sie als ersten Parameter der nächsten Methode zu verwenden.

Somit wird aus:

closures = Enum.map (0..2), &(IO.puts(&1))  

einfach:

closures = (0..2)  
  |> Enum.map &(IO.puts(&1))

Das funktioniert. Die spitzfindigen unter euch könnten jedoch argumentieren, dass hier auch nur Enum.map als Iterator verwendet wird.

Hier noch einmal deutlicher: wir verändern den Aufruf von IO.puts explizit:

closures = []

i = 0  
closures = closures ++ [fn -> IO.puts i end]

i = 1  
closures = closures ++ [fn -> IO.puts i end]

i = 2  
closures = closures ++ [fn -> IO.puts i end]

Enum.at(closures, 0).()  
Enum.at(closures, 1).()  
Enum.at(closures, 2).()  
$ elixir closure_explict.ex
0  
1  
2  

Warum funktioniert das? Schließlich müsste closures doch nun eine Liste von fn -> IO.puts i end haben. Elixir nutzt immutable (unveränderliche) Datenstrukturen. Wenn wir der Variable i einen neuen Wert zuweisen ändern wir nicht den Wert der Variable (Streng genommen werden Variablen in Elixir nicht zugewiesen. Man nutzt "Pattern Matching"). Tatsächlich wird für jede Variable i=1, i=2 und i=3 eine neue Variable i im aktuellen Scope erzeugt. Unsere Funktionen referenzieren die Werte mit dem gleichen Bezeichner i, aber die Werte bleiben unverändert, wenn sie später wieder genutzt werden.

Dieses default-Verhalten kann einem eine Menge Ärger ersparen. Sowohl in single- als auch multi-threaded applications.

Fazit

Beide Sprachen unterstützen Closures. In Elixir sind diese jedoch meines Erachtens nach zugänglicher als in JavaScript. Daher würde ich in JavaScript letztendlich lieber zu diversen Iteratoren greifen, als mich mit unleserlichen Closures herumzuschlagen. So oder so kommt man jedoch nicht umhin sie wenigstens zu verstehen ;)


Export your DayOne Diary to Ghost

As I said before I thought about using Ghost as my main journaling tool. But what about my old DayOne entries? Im journaling since over a year - there are quite a few memories I'd really like to keep.

So what I need is a DayOne to Ghost export tool.

After some research I found out that no one seems to have done the work until now. After a few evenings I may now present the DayoneToGhost export tool.

All you need to do is

$ python dayoneToGhost.py <path to your Journal.dayone>

This will create an dayone_export_<date>.json file and a content directory which contains all your DayOne Photos. All you need to do is to import the dayone_export_<date>.json file and copy the content directory into your Ghost installation directory.

Things that will be exported

  • entry text
  • creation Date
  • images (as post image)
  • tags
  • stars (as featured posts)

Coming soon

  • tag imported entries with a custom tag
  • use external hosted images (to support also hosted ghost blogs)

Currently Missing

  • Wheather information
  • Location information

This are both things the Ghost Data Model doesn't support. Maybe I can fill this gap with the upcoming Ghost Apps...! Make sure to follow me on twitter to get the latest news about the tool.

Big thanks to José Padilla as my tool is based on his tumblr-to-ghost exporting tool.


Bloodborne: mein erster Eindruck

Bloodborne ist also das erste Spiel, welches ich auf meiner PS4 spiele. Ein Spiel, welches teilweise wegen seines hohen Schwierigkeitsgrades negativ beurteilt wurde. Hier nun mein erster Eindruck...

Ohne zu spoilern kann ich sagen: Bloodborne schenkt einem nichts. Man wird in das Spiel geworfen, das Interface wird wenig bis gar nicht erklärt, es gibt keine Tutorials. Dem Spieler wird es selbst überlassen sich alles Stück für Stück beizubringen.

Ehe ich überhaupt verstanden habe wie das Spiel funktioniert, bin ich bereits einige Male gestorben, ohne dem ersten Gegner auch nur ansatzweise ernsthaften Schaden zuzufügen.

Ähnlich ratlos fühle ich mich auch was die Story anbelangt (auch noch nach den ersten Spielstunden). Man findet sich plötzlich in einer Spielwelt, in der unklar ist, wer oder wo der Protagonist ist, warum die Gegner unsere Feinde sind und was überhaupt der Auftrag ist. Mehr als Andeutungen über Jäger, die Nacht der Jagd und damit verbundene, üble Kreaturen gibt es nicht.

Die ersten Spielstunden gestalten sich für mich als ein Mix aus Irren durch die finsteren Straßenzüge der wunderschön in Szene gesetzten, gothischen Stadt Yharnam. Einem ständigen Sich-verloren-fühlen, Herumirren und dann doch wieder Weiterkommen. Die subtile Angst vor potenziell übermächtigen und gleichzeitig wundervoll gestalteten Gegnern, wie sie in meinen schlimmsten Albträumen nicht vorkommen. Schreckensmomente, weil hinter einer Ecke doch wieder ein axtschwingender Henker lauert. Und sich immer breiter machende Verzweiflung, weil der nächste Savepoint vor dem letzten Sterben noch längst nicht in Sicht war und man schon wieder komplett von vorn beginnen musste.

Und auch wenn ich bisher spielerisch kaum positives über Bloodborne berichte, ist mein Eindruck ein unglaublich positiver. Trotz, oder gerade wegen der ständigen Niederlagen ist es ein unfassbares Belohnungsgefühl nach stundenlangem probieren doch wieder ein paar Gegner mehr besiegt, die nächste Abkürzung entdeckt oder wieder ein neues Item gefunden zu haben. Denn eines ist Bloodborne gewiss nicht: unfair.

The Hunter and the Beast

Je länger ich spiele desto mehr bemerke ich, wie mich das Spiel in seinen Bann zieht. Nach circa zwanzig Spielstunden darf ich nun den ersten besiegten Boss verkünden und hatte exakt das gleiche Gefühl, wie ein Reddit-User es beschreibt:

... I just beat the Cleric [Beast, first Boss] and boy does it feel good. My heart was pounding like hell when I got it down to low health and it's been a while since I've been this into a game.
- Niallius on reddit

Bloodborne fordert seine Zeit, bis sich der Spieler mit der Welt und ihren Mechaniken vertraut fühlt. So fiel auch der zweite Boss einen Tag später relativ schnell. Und dennoch weiß ich: das war nur der Anfang. Bloodborne hält noch schlimmere Herausforderungen bereit und es liegen noch einige frustrierende Spielstunden vor mir. Aber eben auch Glücksmomente, wie sie kaum ein anderes Spiel zu erzeugen vermag!

Images from playstation-cs.jp


Nachtrag

Nun liegt auch der dritte Boss. Weiterhin habe ich herausgefunden, dass die PS4 es einem ermöglicht, die letzten 15 Spielminuten als Video zu exportieren. Klasse, noch mehr sinnlose Youtube Videos für das Internet! Hier also mein Boss Kampf gegen das Blood Starved Beast. Achtung, spoiler!


Ghost as a Diary

I am a big fan of the DayOne Journaling App. It has an awesome design, I can write my entries in Markdown and it syncs my Diary between multiple devices. There is a password protection and I don't need to be scared of curious people which temporary use my device.

Unfortunately there are a few downsides which aren't likely to change in the near future:

  • no support for other operating systems than iOS and OSX
  • DayOne supports password protection, but all entries are easily readable since they are saved in plain text
  • DayOnes Update cycles are quite long - there is an update every year or so. New features are quite seldom although DayOne is a highly promoted app in App Stores and Magazines!
  • Limited media in entries. It's one of the most frequent requested DayOne features but even after 3 years it's still not possible to upload more than one picture per entry? What about videos, sounds, gifs? There is so much missed potential!
  • closed source
  • DayOnes approach to become an online diary: The last updates showed that DayOne wants to push more and more into the private blogging/online diary area. Sorry, but I want my Diary stay under my control!

Since Ghost announced password protection I thought about using it as my journaling app of choice.

Pros:

  • accessible from every device which has a web browser + Internet connection
  • insert any media you want
  • entries are under my control and I don't need to use any cloud service
  • the whole diary is customisable to your personal needs (who wants to build a suitable diary theme? :))
  • entries saved in a database - probably more secure than saving entries in plain text in the file system
  • better and more possibilities to work with data
  • Ghost is under more frequent development than DayOne

Downsides:

  • saving pictures is slightly more difficult, especially on mobile (but you can store more pictures per entry ;))
  • currently there are no location details saved (to me a huge disadvantage which maybe can be fixed with the release of Ghost Apps)
  • same for weather information
  • security of your diary relies on the security of ghost (and your webserver)

What do you think about using Ghost as your personal journaling system? Did I miss any pros or cons? Please let me know in the comments!