Lol, yeah Google isn’t very good about searching for programming stuff, DuckDuckGo is actually a LOT better for searching for programming related terms, it even has whole search modes for it that Google doesn’t have. 
The most basic, direct, and unreadable thing would be from Haskells wiki:
Type witness - HaskellWiki
Even OCaml has an example of them in the GADT section of the official spec: OCaml - Language extensions
But in an Elixir world it would be like:
def blah(value, witness), do: witness.(value)
Or in a module form:
def blah(value, witness), do: witness.bloop(value)
In essence a witness is just a type or action that depends on a type.
Typeclasses, like in Haskell, are a non-generic form of witnesses. Like take this function in Haskell:
add :: Num a => a -> a -> a
add l r = l + r
The Num a
is a typeclass, if you call this function like add 1 2
it will return 3
and if you call it like add 1.0 2.0
it will return 3.0
, for any type that fulfills the typeclass Num 'a
. However, look at the =>
, that’s just a special operator in Haskell that means to ‘auto-fill what comes before’, let’s turn that back into a ->
:
add :: Num a -> a -> a -> a
add w l r = (+) w l r
Now you have to call it like add (Num Int) 1 2
to return 3, that Num Int
is the witness, the typeclass is essentially a module reified on that specific type based on the typeclass definition of it, and it passes that module in that location (it’s actually a record in Haskell, but whatever).
In other words, a witness just allows you pass an action over some other type into a function. Haskell’s typeclasses make it “baked in”, in that you can define a witness globally and it can be used globally, but you can’t change it, it is what it is defined as, where if it is something you have to pass in manually, as the OCaml ecosystem does, then you can change it at will, which makes, for example, mocking it absolutely trivial.
Passing in a module or a set of functions into something to operate on a value, whether also passed in or existing entirely internally, but is specified and handled by those functions, makes those functions/module a witness.
In Elixir, I keep my work program very segmented as lots of small dependencies, but they all share the main app’s Repo instead of their own by me defining the MyServer.Repo
module in the global config for each dependency, they then access it just via Application.get_env/2
each time they need it (via a default option on function args, but close enough). In other words I am passing in a witness, the Repo, to ‘witness’ or operator over the data that is being processed, I.E. t he schemas and changesets. This is a pattern I use, probably excessively, because of my OCaml history (super common pattern there), and consequently it makes it sooo easy to ‘mock’ things without needing to grab a code generator like Mox or so.
As an addition, I heavily follow the pattern of almost every function taking a set of optional named arguments, like:
def blah_something(a, b, c opts) do
repo = get_repo(opts)
# Use stuff like `repo.insert/1`
end
Where get_repo
is essentially this defined fairly globally included:
Application.get_env(...) || throw "Repo not set for #{...}"
def get_repo(params, opts \\ []) do
key = opts[:key] || :repo
cond do
is_atom(params[key]) -> params[key] # This actually checks the basic structure of the module
Application.get_env(...) -> Application.get_env(...)
# other stuff
:else ->
Logger.error "blah"
raise %NoRepoException{}
end
end
So I can define a repo globally for a dependency, or I can redefine it on a function-by-function basis, etc… Regardless, it’s just a witness being passed in ‘somehow’ that the functions use to perform work. It’s an exceptionally old pattern in ML languages. 