It's buried in Ribbon Maze, but it's an MIT license, so here are the relevant bits:
==== this goes somewhere in config-initialisation ====
    local fish = {}
    for name,prototype in pairs(game.entity_prototypes) do
        if prototype.type=="fish" then
            table.insert(fish, name)
        end
    end
==== Ribbon Maze then saves "fish" as "config.fishList" ====
==== this generates fish ====
local function generateFish(config, surface, chunkPosition, rng)
    if rng then
        for i = 0, config.fishPerChunk do
            local fishName = config.fishList[Cmwc.randRange(rng, 1, #(config.fishList))]
            local tileX = chunkPosition.x + Cmwc.randRange(rng, 1, 31)
            local tileY = chunkPosition.y + Cmwc.randRange(rng, 1, 31)
            surface.create_entity{name=fishName, position={tileX,tileY}}
        end
    end
end
==== Ribbon Maze is chunk-aligned, so you may want to do things differently ====
Edit:
And of course it loses all the formatting <grumble>