No uměl bych si představit něco takového (hypoteticky):
getRoots :: a : Num -> b : Num -> c : Num -> [x] : [Num] | a * (x * x) + b * x + c = 0
Tak pro inty by to fungovat hypoteticky mohlo. Ale pro floaty si nedokážu představit ani to, jak bych takovýhle předpis ověřoval sám. Minimálně bych tam musel přidat hromadu dalších předpokladů o vstupech a o požadované toleranci.
Právě že by to fungovalo symbolicky pro reálná čísla. Zda by to pak při konkrétní implementaci třeba pro "Num=Double" bylo numericky stabilní je druhá věc.
Dokonalý typový systém (bez rozdílu na jeho druh), tj. takový, který přesně rozpozná chybu, by tedy musel mít kategorie mnohem užší (třeba až do kategorie pro každou hodnotu; včetně jejich vztahů), tedy by NĚJAKÝM způsobem musel duplikovat/nahrazovat testovaný výpočet, jinak by nebyl schopen posoudit správnost.
Třeba ta kvadratická rovnice je zrovna ukázka, že tomu tak vůbec být nemusí.
Na druhou stranu ta ukázka:
Op op a b -> op a b
Tak tady je specifikace a konkrétní řešení identické.
Dneska je tendence když mám zadání alespoň trochu formálně popsané zadání to řešit tak, že si napíšu DSL, a v tom DSL pak popíšu tu specifikaci. A samozřejmě tam většina věcí bude vypadat jako to "op a b" - to prostě "je" ta specifikace. A tam se pak dostáváme k otázce, jak otestovat, že ta specifikace je správně - a tam nějaké testy (nevím, jestli zrovna "unit" testy) jsou asi jediné možné řešení.