Thanks for this spec. Here's a complete, runnable implementation in OCaml:

type walker = WalkFn of (animal -> unit)
and jumper = JumpFn of (animal -> unit)
and swimmer = SwimFn of (animal -> unit)
and intoxicator = ToxicFn of (animal -> animal -> unit)
and animal = Animal of animal_data
and animal_data = {
name: string;
color: string;
mutable walk: walker option;
mutable jump: jumper option;
mutable swim: swimmer option;
mutable intoxicate: intoxicator option
}

let can_walk = function
| Animal { walk = Some _ } -> true
| _ -> false

let walk animal =
match animal with
| Animal { walk = Some (WalkFn walk_fn) } -> walk_fn animal
| _ -> invalid_arg "this animal can't walk"

let default_walker = function
| Animal { name; color } -> Printf.printf "the %s %s walks\n" color name

let allow_to_walk (Animal data) =
data.walk <- Some (WalkFn default_walker)

(* --- more of the same for the other actions --- *)
let can_jump = function
| Animal { jump = Some _ } -> true
| _ -> false

let jump animal =
match animal with
| Animal { jump = Some (JumpFn jump_fn) } -> jump_fn animal
| _ -> invalid_arg "this animal can't jump"

let default_jumper = function
| Animal { name; color } -> Printf.printf "the %s %s jumps\n" color name

let allow_to_jump (Animal data) =
data.jump <- Some (JumpFn default_jumper)

let can_swim = function
| Animal { swim = Some _ } -> true
| _ -> false

let swim animal =
match animal with
| Animal { swim = Some (SwimFn swim_fn) } -> swim_fn animal
| _ -> invalid_arg "this animal can't swim"

let default_swimmer = function
| Animal { name; color } -> Printf.printf "the %s %s swims\n" color name

let allow_to_swim (Animal data) =
data.swim <- Some (SwimFn default_swimmer)

let can_intoxicate = function
| Animal { intoxicate = Some _ } -> true
| _ -> false

let intoxicate animal victim =
match animal with
| Animal { intoxicate = Some (ToxicFn toxic_fn) } -> toxic_fn animal victim
| _ -> invalid_arg "this animal can't intoxicate"

let default_intoxicator animal victim =
match animal, victim with
| Animal attacker, Animal victim ->
Printf.printf "the %s %s poisons the %s %s\n"
attacker.color attacker.name victim.color victim.name

let allow_to_intoxicate (Animal data) =
data.intoxicate <- Some (ToxicFn default_intoxicator)
(* --- end of actions --- *)

let create_frog () =
Animal {
name = "frog";
color = "black";
walk = None;
jump = None;
swim = None;
intoxicate = None
}

let create_cow () =
Animal {
name = "cow";
color = "green";
walk = None;
jump = None;
swim = None;
intoxicate = None
}

let story () =
let frog = create_frog () in
Printf.printf "newborn frog, can it walk? %s\n"
(if can_walk frog then "yes" else "no");
allow_to_walk frog;
Printf.printf "and now, can it walk? %s\n"
(if can_walk frog then "yes" else "no");
walk frog;
allow_to_swim frog;
swim frog;
allow_to_jump frog;
jump frog;
allow_to_intoxicate frog;
intoxicate frog (create_cow ());
Printf.printf "the end\n"

(* now run all this *)
let _ = story ()

And here's what it looks like when it's run:

$ ocaml frog_story.ml
newborn frog, can it walk? no
and now, can it walk? yes
the black frog walks
the black frog swims
the black frog jumps
the black frog poisons the green cow
the end

It's true that the animal type must have types and fields for all possible actions an animal might ever take. It would be possible to compartmentalize things a bit more, maybe. But for a quick prototype this should work quite well, I think.