Pattern for literals that need validation

I have an opaque type Nineagram.NineagramPuzzle, and to get one of these values I have to call Nineagram.fromString, and the string has some conditions, so it returns a Result Nineagram.CreationProblem NineagramPuzzle.

In my Main.elm view if the user hasn’t entered a puzzle to solve yet (model.puzzle == Nothing), I want to just render the usual puzzle view with some default puzzle serving as a placeholder, and so I really just wanted a NineagramPuzzle literal in Main.elm for that purpose, but of course I can’t construct that value from opaque type.

The solution I came up with was a pattern where I construct the literals on init and then return a model representing a failure if the literals are not valid, otherwise it returns a model that has the desired inital state plus the literals. Then I updated view to either show an error on the failure state, or call a separate view which accepts the valid literals as an additional argument: viewState : Constants -> State -> Html Msg

Here’s the diff for my app: https://github.com/bukkfrig/nineagram-solver/commit/eae9d2da58630991b1248549363acd367b54c699

If I had tests set up, I could just add a test that init () produces the good model and not the bad one to validate my literals.

This is probably overengineering for this simple program, but is this approach sensible for larger programs that need literals that can’t be simply type checked? Have you used a similar approach before?

1 Like

I think the approach is valid and makes sense :+1:

When dealing with literals, I found (what I call) the Safe Unsafe Pattern with elm-review to be quite effective. It’s very likely overkill for your situation because you only have a single location where this happens. And especially so since you already have a test giving you all the guarantees you’re looking for.

For this situation, I think your current solution is :ok_hand: though.

1 Like

Oh, I saw from your link, you can just define it recursively…

defaultPuzzle : () -> NineagramPuzzle
defaultPuzzle _ = 
    case Nineagram.fromString "GRNAMNIEA" of
        Ok puzzle ->
            puzzle
        
        Err _ ->
            defaultPuzzle ()

As long as I have a test that evaluates it, and as long as I’m OK with the testrunner crashing when I break it, perhaps that could be the simplest way?

That is not the solution I meant, but yeah that also works :grinning_face_with_smiling_eyes:
Without a test that seems very dangerous, but with one I guess it’s okay, as long as you (and your teammates) are well aware how it may break one day.

1 Like

I think the approach you had before with Nineagram.defaultPuzzle was fine! Simple and safe. (Maybe it should be called Nineagram.placeholderPuzzle, though.)

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.