2021-08-04 02:19:59 +03:00
|
|
|
# Design decisions
|
|
|
|
|
|
|
|
## Why not FRP?
|
|
|
|
|
|
|
|
I wanted to create an easy to use and learn library, and I found FRP was harder
|
|
|
|
to explain than the Elm Architecture.
|
|
|
|
|
|
|
|
## Why do widgets use hidden internal state?
|
|
|
|
|
|
|
|
Some widgets need state to operate. For example, textFields need to know the
|
|
|
|
current cursor position and selection, while images need to have a reference to
|
|
|
|
the block of bytes representing the uncompressed image. There are two options
|
|
|
|
for handling this requirement:
|
|
|
|
|
|
|
|
### Make the state explicit
|
|
|
|
|
2021-08-05 00:33:02 +03:00
|
|
|
Users take care of storing and providing the state to the library. This is
|
2021-08-04 02:19:59 +03:00
|
|
|
similar to how the model's fields are associated to widgets.
|
|
|
|
|
2021-08-05 00:33:02 +03:00
|
|
|
The advantage is that this makes the library more functionally pure. It also
|
2021-08-08 17:51:30 +03:00
|
|
|
simplifies the internals and may improve performance, since the merge process
|
|
|
|
could be simplified.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
The disadvantage is that it requires more boilerplate from part of the user,
|
|
|
|
since fields for each of the widget's state need to be added to a global state
|
|
|
|
type (or mix it with the model).
|
|
|
|
|
|
|
|
### Keep the state internal
|
|
|
|
|
2021-08-05 00:33:02 +03:00
|
|
|
Widgets have an internal state that is saved/restored by the library.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
The main advantage is that it's simpler for the user. Only widgets that handle
|
2021-08-05 00:33:02 +03:00
|
|
|
input require the user to provide them model fields, while their internal state
|
|
|
|
is transparent from the user's point of view.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-05 00:33:02 +03:00
|
|
|
The disadvantages are that it makes the library more complex and less
|
2021-08-04 02:19:59 +03:00
|
|
|
functionally pure than the other approach. It also makes creating custom widgets
|
|
|
|
a bit more complex, since the merge process needs to be considered.
|
|
|
|
|
|
|
|
### What this library does
|
|
|
|
|
|
|
|
As mentioned in the README, one of the main objectives of the library is being
|
|
|
|
easy to use. For this reason, and because it reduces boilerplate, hidden state
|
|
|
|
was chosen. The library takes care of saving/restoring state during the merge
|
|
|
|
process.
|
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
Sometimes, for performance reasons, you may want to use `mergeRequired` as the
|
|
|
|
[Books](examples/02-books.md#interesting-bits) example does.
|
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
## Are themes really necessary?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
While it's true that you can create a customized version of a widget by writing
|
|
|
|
a regular function, and it is in fact encouraged and used in the examples, in
|
|
|
|
some cases it is not enough.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
Some widgets, such as `alert` and `confirm`, are built out of nested widgets and
|
2021-08-09 00:17:21 +03:00
|
|
|
do not expose styling options for all their sub widgets. In this scenario,
|
|
|
|
themes allow you to customize those nested widgets as needed.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
Themes also have the nice property of simplifying color scheme switching.
|
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
## Why there is not a margin property, considering border and padding do exist?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
Margin used to be part of the library, but made mouse event handling more
|
|
|
|
complex. Since margin is just space outside the border, it can be emulated
|
|
|
|
by adding a wrapper widget. For example:
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
```haskell
|
|
|
|
box (label "Test" `styleBasic` [padding 10, border 1 black])
|
|
|
|
`styleBasic` [padding 5]
|
|
|
|
```
|
|
|
|
|
|
|
|
In that case, the box provides the _margin_ around the label.
|
|
|
|
|
|
|
|
## Why is the exit event sent only to the top level widget?
|
|
|
|
|
|
|
|
Adding a SystemEvent was discarded because most widgets would not be interested
|
|
|
|
in handling it. With that in mind, relying on messages or regular events made
|
|
|
|
more sense.
|
|
|
|
|
|
|
|
Considering there is not broadcasting support for messages (they are sent to
|
|
|
|
a widget on a given path), restricting to the top level widget seemed clearer.
|
|
|
|
|
|
|
|
## Why, except for render, is the widget interface non monadic?
|
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
I find using regular functions nicer than using bind or do syntax and, given
|
|
|
|
those functions do not rely on any effect, I preferred to keep them as such.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
## Why is the return type of the render function IO and not some custom monad?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
I didn't feel having a custom monad provided a benefit, considering the Widget
|
2021-08-09 00:17:21 +03:00
|
|
|
Environment parameter is already available (if it hadn't been a parameter, a
|
|
|
|
Reader stack may have been a good idea).
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
## Why is the return type of Tasks and Producers IO and not MonadIO?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
There were some type mismatch issues down the road plus the async library
|
2021-08-08 17:51:30 +03:00
|
|
|
requires using IO to launch asynchronous tasks. Maybe MonadBaseControl or
|
|
|
|
MonadUnliftIO could be used.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
## Why does WidgetNode have a reference to children widget, considering the widget may not have any?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
It made development of container widgets simpler. It also gave me the ability to
|
|
|
|
create utility functions that can be used across the library for inspecting the
|
|
|
|
widget node tree.
|
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
## Why is the location of the optional config argument inconsistent between single and container widgets?
|
2021-08-04 02:19:59 +03:00
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
For single widgets having the optional config as the last argument makes sense,
|
|
|
|
since it is something extra compared to the default version.
|
|
|
|
|
|
|
|
For container widgets the usage pattern is a bit different. While splitting a
|
|
|
|
complex UI into smaller parts is necessary for maintainability, being able to
|
|
|
|
see the widget hierarchy at first glance is also useful for understanding an
|
|
|
|
application. For this reason, sometimes, widgets are nested directly in their
|
|
|
|
parent instead of creating a separate list that is later provided to the parent
|
|
|
|
widget. In that scenario, having the config of the parent widget _after_ its
|
|
|
|
children causes confusion, because it's not clear whom the config belongs to.
|
|
|
|
Putting the config before the children is much clearer in this regard.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
## Why records of functions instead of typeclasses?
|
|
|
|
|
|
|
|
There were two reasons for using records instead of typeclasses:
|
|
|
|
|
|
|
|
- Since all widgets end up inside a list, the instances of those typeclasses
|
2021-08-09 00:17:21 +03:00
|
|
|
would need to be wrapped in an existential, losing their specific types in the
|
2021-08-04 02:19:59 +03:00
|
|
|
process.
|
|
|
|
- Using typeclasses for widgets seemed to lead to needing more advanced type
|
|
|
|
system features.
|
|
|
|
|
2021-08-09 00:17:21 +03:00
|
|
|
Because of the first reason, there were not advantages by using typeclasses
|
|
|
|
compared to using records. This situation of wrapping a typeclass in an
|
2021-08-10 00:57:41 +03:00
|
|
|
existential is referred to as _"Existential Typeclass Anti Pattern"_. For
|
2021-08-09 06:26:35 +03:00
|
|
|
example,
|
2021-08-04 02:19:59 +03:00
|
|
|
[here](https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass).
|
|
|
|
|
|
|
|
## Why do you use lawless typeclasses for combinators?
|
|
|
|
|
|
|
|
I wanted to provide a standardized interface for configuring widgets. For
|
2021-08-09 00:17:21 +03:00
|
|
|
example, all widgets which generate events when clicked should expose a
|
|
|
|
configuration option using the same name (in this case `onClick`). I think the
|
|
|
|
only options to achieve this are typeclasses and labels, and I chose typeclasses
|
|
|
|
because I know them better.
|
|
|
|
|
|
|
|
This choice may also cause namespace issues that require using qualified
|
|
|
|
imports. I tried to avoid _reserving_ common names to reduce the number of times
|
|
|
|
this is required.
|
2021-08-04 02:19:59 +03:00
|
|
|
|
|
|
|
If you know a better option to solve this, please let me know!
|
|
|
|
|
|
|
|
## Why Lens and not MicroLens?
|
|
|
|
|
2021-08-08 17:51:30 +03:00
|
|
|
I needed to store references to lenses in custom data types, which is not
|
|
|
|
possible with the standard `Lens` type. The lens library provides `ALens`, which
|
|
|
|
allows for that use case. I don't think microlens has something similar, plus
|
|
|
|
I'm currently using more things from lens.
|