[BRLTTY] BrlAPI for Haskell

Aura Kelloniemi kaura.dev at sange.fi
Fri Feb 11 07:45:31 EST 2022


Hello,

On 2022-02-11 at 10:27 +0100, Mario Lang <mlang at blind.guru> wrote:
 > Aura Kelloniemi <kaura.dev at sange.fi> writes:
 > > There are actually a few modes in which a BrlAPI connection can be. I would
 > > implement type classes for the common parts of these modes, and have a
 > > separate handle for each mode (at least basic, tty, raw and tty with raw keycodes).

 > I think the common operations can still use the initial handle.

That depends on the state of the connection. For example when the connection
is in raw or suspend mode, many operations just return an error.

 > Typeclasses are neat, but rather questionable without any underlying laws.

That is the longstanding debate in the Haskell world about how to use type
classes. I do not wish to dive deep into arguing about that, for example
because I am not a mathematician and not an expert of the topic. But the laws
don't necessarily need to be ery complicated. THe classes can be something
like this:

class HasDisplaySize con where
  getDisplaySize :: (HasBrlAPIConnection con, MonadIO m) => con -> m (CellCount, CellCount)

I don't see how this is more questionable than type classes such as HasField
or Storable which are part of the base package. Also it pretty much looks like
the code style suggested by many industrial Haskell advocates.

When a type class has only one method, there are no interactions between
different methods and thus the associated law is more or less the type
signature of the single method.

As another example, brlapi__readKey returns a very different kind of value
depending on the state of the connection. In tty mode it gives out a BRLTTY
command with associated flags and other data. In raw tty mode the code is
completely driver specific and cannot be decoded with the same logic as the
BRLTTY command code. A safe interface to BrlAPI would make it difficult for the
client to mix and match these different types of key codes on type level.

Also, a safe interface might prevent the client from requesting a parameter
value from the library, while the connection clearly cannot provide it.

The point of type safety is to make invalid states non-representable, but due
to the nature of BrlAPI, this is a challenging task.

If you don't like overloading method names with type classes, there are other
options, like creating separate connection handle types for most operations,
and providing wrapper sum types, like this:

getDisplaySize :: BrlapiConnectionWithDisplaySize -> IO (CellCount, CellCount)

data BrlapiConnectionWithDisplaySize
    = BasicConnectionWithDisplaySize BasicConnection
    | TtyModeConnectionWithDisplaySize TtyModeConnection
    | DriverTtyModeConnectionWithDisplaySize DriverTtyModeConnection
    -- Note how there is no entry for raw or suspended mode connection so
    -- that those connections cannot be used to call getDisplaySize.

However, we probably agree, that this style is horribly verbose and
complicated for the client programmer, even though it is safer than using just
one handle type.

 > > In addition I would export the low-level C functions from a separate module,
 > > in case somebody needs them, or the thick binding does not cover 100% of all
 > > possible API use cases.

 > Hmm, good point.  The FII is already in a separate module, but I am
 > currently leaning towards not exporting that.

Well, it can be exported as part of a .Internal module hierarchy so that API
compliancy does not need to be a concern.

 > > I'm very interested in this project. I try to push my Rust bindings to github
 > > soon, after some clean up. Possibly Rust and Haskell can share some ddesign
 > > decisions.

 > While Rust and Haskell seem to be compared a lot lately, I personally
 > dont see the commonalities.

The type systems of these languages are quite similar. BrlAPI operations are
pretty simple – they are all IO actions, so purity goes out of the window
together with laziness.

Rust has its ownership system which allows swallowing any old connection
handles when the connection changes mode so that the client cannot use stale
old connection handles to make calls to BrlAPI. Linear and higher kinded types
can be used to achieve a similar effect in Haskell. But yes, that is a
significant differene.

Of course the applications that utilize BrlAPI will most likely be quite
different (and written in different style) depending on whether they are
written in Rust or Haskell. BrlAPI, however, does not benefit a lot about
higher level abstractions such as iterators or streaming.

So basically I'm talking about type safety of the low-level BrlAPI operations
and how to encode them with a type system that is somehow based on
Hindley-Milner. It is completely a different story if we start to build a
complete user interface layer on top of the basic device control interface
provided by BrlAPI, but as far as I understand, this is not the topic now.

 > There is laziness, which can be useful but also an unexpected beast. And of
 > course there is purity, which Rust doesn't really have to care about. The
 > safety Rust brings you mostly comes from borrowing. The functional features
 > of Rust are mostly syntactic sugar to me. True, the iterator API and
 > slightly more expression-oriented syntax makes it feel like something one
 > might have seen in more functional languages. But to me, Rust is still a
 > fancy C with better compile-time checking.

I agree here, but in my opinion BrlAPI is a device control interface. Whether
somebody wants to use it as is, or build a functional reactive toolkit on top
of it, they still benefit if the low-level API prevents them from accessing
the connection handle in inapropriate ways.

 > > I'm especially interested in how are you planning to handle the
 > > parameter API in a typesafe way, including the parameter state change
 > > callbacks.

 > Frankyl, one step at a time.  I have no full laid out plan on how to do
 > the complete bindings yet.

No need to. I was just asking, because when I met the parameter interface,
I really started to pull hair off my head. The parameters API is very C'ish.

While working with the Rust bindings, I needed to change my mind regarding the
end-user API many times due to the new complexities of BrlAPI that came
across. I'm still not completely sure which BrlAPI methods are available in
which connection modes. Sometimes it is necessary to look at the server code
to get this information. I wish it was documented.

-- 
Aura


More information about the BRLTTY mailing list