Tuesday, May 19, 2015

representable functors

A nugget of insight about representable functors from The Catsters, episode Representables and Yoneda 3, around minute 1:00.

as it turns out, these are very very closely controlled, there's very little that they can do and they are completely controlled by very small amounts of data [...] we think that a lot is going on, when in fact we have very little choice in what's going on, and everything that's going on everywhere is just controlled by one really small bit   

Wednesday, May 13, 2015

a cabal sandbox for scripting

Later edit: this post is now obsolete, thanks to Stack.

As a way to facilitate Haskell scripting, I have created the hs-scripts repo on GitHub.

The idea is being able to run interpreted Hakell scripts, without having to install any package at the global level.

To test it, clone the repo, create a sandbox, and install the library. The repo should also be added to the PATH variable.

The library is empty at the moment, but is intended to hold common functions and definitions shared by multiple scripts.

You can invoke cabal repl in the repo to open a GHCi session with a lot of useful packages available (a poor man's Haskell Platform, if you will).

There are two batch scripts, hs.bat for Windows and hs.sh for Linux. They take a Haskell script name (without extension) as parameter and execute the script in the context of the sandbox. Scripts are searched in the current directory first and then in the /scripts subfolder of the repository.

Scripts are easy to compile, if necessary.

So far there's only one script, a replica of Python's ever-useful python -m SimpleHTTPServer.

Friday, April 24, 2015

Videos for "Structure and Interpretation of Computer Programs" (1986)

  • 1A
  • 1B
  • 2A
  • 2B
  • 3A Around 1:08, the idea of program design as creating layers of languages.
  • 3B
  • 4A
  • 4B
  • 5A Interesting that they don't introduce assignment and mutability until this lecture.
  • 5B
  • 6A
  • 6B Around minute 38, a mention of Miranda, a predecessor to Haskell. Lazy evaluation as "decoupling time in the programs from time in the machine". Explanation of the disadvantages of lazy evaluation (incompatibility with side effects, "dragging tails" a.k.a. space leaks).
  • 7A Meta-circular evaluator, fixed points, Y combinator, denotational semantics.
  • 7B "There are many languages that have made a mess of themselves by adding huge numbers of features". Implementing dynamic binding and call by name.
  • 8A
  • 8B
  • 9A
  • 9B
  • 10A
  • 10B
A version of the book in .mobi format (suitable for Kindle) can be found here.

Sunday, March 29, 2015

Is ExceptT over IO really an anti-pattern ?

I was reading this exceptions best practice guide for Haskell. It contains good advice, for example about how masking all exceptions is a terrible idea. However, I sort of disagree about the "ExceptT over IO being an antipattern" part.

One of the reasons given is that "it's non-composable". I actually think that making errors explicit in the types can be more composable, especially if you want to combine or transform the errors in some  manner. Wrapping an exception to provide additional context tends to be more cumbersome than mapping over the error type with withExceptT (or first from Bifunctor).

When you compose multiple exception-throwing functions, if you aren't careful your result funcion will end up  (maybe even unbeknownst to you!) throwing a zoo of unrelated exceptions with scant context.

(Digression: the recent pipes-cliff library actually does a good job in wrapping every IOException that it encounters, tagging it with extra information.)

On the other hand, I admit that making all the errors explicit in the signature complicates it, and it becomes annoying if you consider those errors as "fatal" anyway and you don't intend to handle them, or at least not at the current layer.

Which brings us to the other main criticism: that using "ExceptT over IO" doesn't really ensure the absence of exceptions, even if it seems to imply it, and that it only provides a second, largely redundant, channel for communincating errors. In my opinion, there can be cases when we might want two separate error channels: if we want to distinguish between outright fatal vs. recoverable errors that we may want to handle, for example. Or between errors we want to annotate/map over/compose easily versus errors we just want to pass to upper layers unchanged.

In my process-streaming and conceit libraries I actually employ this dual approach. The execution functions re-throw any IO exceptions they encounter, but you can also have an explicit error type. If case the error type is never used, it remains polymorphic and we can use functions that have a simpler signature but require the error type to unify with Void.

Sunday, February 22, 2015

A dynamically generated FromJSON instance

Suppose you need to invoke a function (one like decode from pipes-aeson) that has a FromJSON constraint, but the exact parsing method required for the JSON is not fully known until runtime. Perhaps because field names in the JSON have some prefix that is only determined through reading  a configuration file, or by asking the user.

One solution is to use the tools provided by reflection package to dynamically generate the FromJSON instance at runtime, after having constructed the parse function.

The reflection repository already contains an example of a dynamically generated Monoid instance. I have adapted it for the FromJSON case; the code can be found in this gist.

Some tips for defining these kinds of local instances:

  • You never ever have to declare a Reifies instance, this is done by the internal machinery of the reflection package, through some dark magic I don't understand, even if it's explained here.
  • Instead, you use Reifies as a precondition for the instance you are actually declaring (FromJSON in this case). You are expressing something like "whenever this phantom type s reifies a typeclass dictionary at the type level, I can reflect it back and use the recovered dictionary to implement the methods of my instance".
  • The actual value passed to the reflect function is ignored, it only serves to tell the function what type reifies the value we want to reflect. Assuming you have a Reifies s a constraint, you can always pass something like Proxy :: Proxy s to reflect and obtain an a.
  • At some point, you will need a small auxiliary function to convince the compiler that the phantom type in your datatype is the same as the phantom type in the Proxy supplied by reify. Otherwise the Reifies constraint won't be satisfied, and your instance won't kick in!
  • Notice that, thanks to parametricity, the phantom type in the Proxy supplied by reify can never escape the callback. So be sure to strip from your result any newtype that still carries the phantom type!
See also this answer on Stack Overflow.