Skip to content

nucleotide-count exercise could be much simpler with a change in signatures #559

@hyphenrf

Description

@hyphenrf

Problem: currently the exercise forces reliance on the Base API, which is fairly complicated. Learners are to be introduced to advanced features like first-class modules, polymorphic variants, phantom types, and their comparator design pattern with scarce docs about it in the API pages etc. The learner thus has to divert attention from the core problem to contend with these concepts. Additionally and unlike the rest of exercises, dealing with Base here is not optional; the return type of count_nucleotides is a (int Map.M(Char).t, char) Result.t.

Reports of the confusion can be seen in #433, especially that Base design choices for similarly named functions often conflict with the OCaml manual and Stdlib docs, which sends mixed signals to people whose exposure to the language is first and foremost through exercism. OCaml manual suggests module CharMap = Map.Make(Char), Base offers let charmap = Map.empty (module Char) which differs with the similarly named Stdlib CharMap.empty that a learner could try in the REPL, and then there's the module CharMap = Map.M(Char) functor which is visible in the interface file, but doesn't produce a usable module.

Suggestions: one or more of the following could greatly simplify the exercise

  • avoid a specific data structure in the return type of count_nucleotides, instead normalize to e.g. (char * int) list or some iterator, and let the learner pick the intermediate data structure as they please (hashtbl, map, assoc list, a tuple, ...)
  • define own return type e.g. type counts = { a : int; c : int; g : int; t : int } or type counts = int CharMap.t where module CharMap = Map.Make(Char) (from stdlib) and use that
  • define an exception e.g. exception Invalid of char and let learners raise instead of returning Result.t and having to deal with its monadic nature
  • define a minimal interface for a generic Map module e.g. just val to_list : 'a t -> (char * 'a) list and let users pick whatever implementation satisfies it.

Possible example.ml:

module CharMap = Map.Make(Char)

exception Invalid of char

let check c =
  if not (String.contains "ACGT" c) then raise_notrace (Invalid c)

let count_nucleotide s c =
  check c;
  String.fold_left
    (fun count c' -> check c'; if c = c' then count + 1 else count) 0 s

let count_nucleotides =
  String.fold_left
    (fun counts c ->
      check c;
      CharMap.update c (Option.fold ~some:(fun n -> Some (n + 1)) ~none:(Some 1)) counts)
    CharMap.empty

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions