I use an Elm-powered CLI to plan work using an algorithm similar to Mark Forster’s Final Version system. This algorithm relies heavily on bisecting a list—it’s all about selecting the first inactive todo before all active todos, that sort of thing.
Prior to today, I was doing this with a lot of List
work, which I was not happy with. it obscured my intent in a lot of case
matching.
But as of today, I’ve made a DSL that makes performing the required bisections pretty nice. Using it ends up looking like this:
startSelecting : Todos -> Maybe ( Todos, Todo )
startSelecting todos =
todos
|> slice
|> before First Filter.isWorkable
|> last Filter.isSelectable
|> map Todo.activate
I’m really happy with this! It’s way better than the deep nesting I was having to do before. Here’s how the API looks:
type Slice quantity -- opaque
slice : Todos -> Slice Multiple
type Multiple -- opaque
type Position = First | Last
before : Position -> Filter -> Slice Multiple -> Slice Multiple
after : Position -> Filter -> Slice Multiple -> Slice Multiple
getList : Slice Multiple -> List Todo
type Single -- opaque
first : Filter -> Slice Multiple -> Slice Single
last : Filter -> Slice Multiple -> Slice Single
get : Slice Single -> Maybe Todo
map : (Todo -> Todo) -> Slice Single -> Maybe ( Todos, Todo )
I’m particularly proud that the types here prevent us from doing the wrong terminal query (get
vs getList
) if the list is insufficiently narrowed or over-narrowed. This is currently pretty specific to my code (e.g. I never need to modify more than one todo at once, so I don’t.) It could be made more general, like this:
type Slice a quantity -- opaque
slice : List a -> Slice a Multiple
type Multiple a -- opaque
type Position = First | Last
before : Position -> (a -> Bool) -> Slice a Multiple -> Slice a Multiple
after : Position -> (a -> Bool) -> Slice a Multiple -> Slice a Multiple
getList : Slice a Multiple -> List a
type Single a -- opaque
first : (a -> Bool) -> Slice a Multiple -> Slice a Single
last : (a -> Bool) -> Slice a Multiple -> Slice a Single
get : Slice a Single -> Maybe a
map : (a -> a) -> Slice a Single -> Maybe ( List a, a )
If this pattern matches some work that you’re currently doing, the (non-generic) code is here: https://github.com/BrianHicks/daily/blob/61372bc7c851d686fe6917bfbe78d12505e5879d/src/Slice.elm If this turns out to be helpful for someone else, I’m happy to make it into a package so we can both use it.