-
-
Notifications
You must be signed in to change notification settings - Fork 55
Description
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) listor 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 }ortype counts = int CharMap.twheremodule CharMap = Map.Make(Char)(from stdlib) and use that - define an exception e.g.
exception Invalid of charand let learners raise instead of returningResult.tand having to deal with its monadic nature - define a minimal interface for a generic
Mapmodule e.g. justval to_list : 'a t -> (char * 'a) listand 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