{"id":16313999,"url":"https://github.com/lysxia/metamorph","last_synced_at":"2025-03-22T20:35:46.306Z","repository":{"id":149400093,"uuid":"77760102","full_name":"Lysxia/metamorph","owner":"Lysxia","description":"Monomorphize polymorphic functions for testing","archived":false,"fork":false,"pushed_at":"2017-01-17T19:08:42.000Z","size":59,"stargazers_count":35,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-18T14:49:14.865Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lysxia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-01T00:06:50.000Z","updated_at":"2024-12-02T13:28:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"d6dd1d99-bff3-4122-8ad1-a5aa29fe603a","html_url":"https://github.com/Lysxia/metamorph","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fmetamorph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fmetamorph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fmetamorph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lysxia%2Fmetamorph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lysxia","download_url":"https://codeload.github.com/Lysxia/metamorph/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245020172,"owners_count":20548155,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T21:52:55.076Z","updated_at":"2025-03-22T20:35:46.229Z","avatar_url":"https://github.com/Lysxia.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"Test Polymorphic Functions with Metamorph\n=========================================\n\nI give you an unknown function:\n\n```haskell\nf :: forall a. a -\u003e (a -\u003e a) -\u003e a\n```\n\nBy looking at its type, you know it can do only one thing: iterate the second\nargument on the first one a fixed number of times. You can easily extract that\nnumber out of the function by instantiating `f` at the type of integers, and\nby applying it to `0` and `succ`.\n\n```\n\u003e f 0 succ :: Integer\n```\n\n    3\n\nFrom the type of `f` and a single value, you can deduce that `f` just iterates\na function three times:\n\n```haskell\nf :: forall a. a -\u003e (a -\u003e a) -\u003e a\nf a r = r (r (r a))\n```\n\nAny polymorphic function is similarly restrained by its type in the range of\npossible behaviors it can have, and we can restrict it further by observing\nsome of its outputs.\n\nGiven a universally quantified type, e.g., `forall a. a -\u003e (a -\u003e a) -\u003e a`,\n`metamorph` can craft a type `A` and special inputs to perform a sort of\nsymbolic evaluation of functions of that type.\n\nReverse engineer functions\n--------------------------\n\nFunctions of certain types, such as `forall a. a -\u003e (a -\u003e a) -\u003e a`, are\nuniquely determined by the image of a single well chosen input. In that case,\nwe can guess the definition of a polymorphic function with `prettyMorphing`:\n\n```\nprettyMorphing :: (...) =\u003e String -\u003e monomorphizedType -\u003e String\nprettyMorphing :: String -\u003e (A -\u003e (A -\u003e A) -\u003e A) -\u003e String`\n\n\u003e prettyMorphing \"f\" (f :: A -\u003e (A -\u003e A) -\u003e A)\n```\n\n    \"f a b = b (b (b a))\"\n\nRandom evaluation\n-----------------\n\nIn other cases, `prettyMorphing` won't type check. You may try to use\n`morphingGen` to generate some random inputs. Thanks to QuickCheck, even\nfunctions can be generated!\n\nFor any function type `F a` and an associated `A` constructed by `metamorph`:\n\n```haskell\nmorphingGen :: F A -\u003e Test.QuickCheck.Gen (Domain (F A), Codomain (F A))\n```\n\nFor example, if `F a = [a] -\u003e [a]`, then `Domain (F A) ~ ([A], ())` and\n`Codomain (F A) ~ [A]`.\n\n```\n\u003e generate (morphingGen (reverse :: [A] -\u003e [A])) \u003e\u003e= \\((a, _), c) -\u003e\n\u003e   putStrLn $ \"a@\" ++ pretty_ a ++ \" -\u003e \" ++ show c\n```\n\n    a@[_,_,_,_] -\u003e [a !! 3,a !! 2,a !! 1,a !! 0]\n\nSome list `a` of length 4 was generated, and this shows that the image of `a` under\n`reverse` is indeed its reverse.\n\nWe use `pretty_` instead of `show` to display the input list with minimal\nnoise.\n\nExamples can be found under `test/`.\n\n---\n\n```haskell\n-- No Template Haskell! Magic!\n\n{-# LANGUAGE TypeFamilies #-}\n\nimport Data.Functor.Identity\nimport Test.Metamorph\n\ntype F a = a -\u003e (a -\u003e a) -\u003e a\n\n-- This is our polymorphic function.\nf :: {- forall a. -} F a\nf a r = r (r (r a))\n\n-- The only ingredient you need to invent is the type synonym F.\n-- Everything else (Metamorph, Newtype) belongs to Test.Metamorph.\n-- The first incantation is one newtype and a trivial (enough) instance\n-- to unwrap it.\n\nnewtype A' = A' (F (Metamorph A'))\n\ninstance Newtype A' where\n  type Old A' = F (Metamorph A')\n  unwrap (A' a) = a\n\n-- This is the type at which to instantiate f.\n-- It is Show-able.\ntype A = Metamorph A'\n\n-- Monomorphization!\nf_ :: A -\u003e (A -\u003e A) -\u003e A\nf_ = f\n\n-- For this type, you only need to look at a single value to deduce everything\n-- about f. metamorph implicitly and purely computes an ideal pair (A, A -\u003e A)\n-- to be applied to f.\n--\n-- 'morphingPure' applies these arguments to the function, and returns the\n-- result.\n--\n-- \u003e morphingPure :: F A -\u003e A\n--\n-- 'prettyMorphing' produces a possible definition of the function.\n--\n-- \u003e prettyMorphing :: String -\u003e F A -\u003e String\n--\nmain = do\n  print (morphingPure f_)            -- b (b (b a))\n  putStrLn (prettyMorphing \"f\" f_)   -- f a b = b (b (b a))\n```\n\nFuture work\n-----------\n\n- Documentation.\n\n- We could also enumerate inputs.\n  Even if there may be more than one necessary value to observe,\n  there are many cases where they live in a very small space;\n  e.g., `forall a. [a] -\u003e [a]`, only one list of each length is necessary\n  to identify a function of that type.\n\n- Generic implementation for algebraic data types.\n  The type `F` must currently use only:\n\n  - `()`, `Bool`, `Integer`, `Int`,\n    `(-\u003e)`, `(,)`, `Either`, `Maybe`, `[]`.\n\nP.S.\n----\n\nIf you have two or more type parameters:\n\n```haskell\ntype G2 a b = (a -\u003e b -\u003e b) -\u003e [a] -\u003e b -\u003e b\n```\n\nMerging them will do the right thing:\n\n```haskell\ntype G a = G2 a a\n```\n\nReference\n=========\n\n- [*Testing polymorphic properties.*](http://publications.lib.chalmers.se/records/fulltext/local_99387.pdf)\n  Jean-Philippe Bernardy, Patrik Jansson, and Koen Claessen. 2010.  \n  In Proceedings of the 19th European conference on\n  Programming Languages and Systems (ESOP'10), Andrew D. Gordon (Ed.).\n  Springer-Verlag, Berlin, Heidelberg, 125-144.\n\nThe main idea comes from this paper: we can construct a \"good\" type to test\npolymorphic functions on. The given construction is decomposed into simple\noperations on types, which works well for proofs, but that seems tough to\nimplement in Haskell.\n\n`metamorph` takes a more streamlined approach centered on the notion of\nsymbolic evaluation. Interestingly, it turns out that GHC's type families\nsuffice to implement this without using Template Haskell.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flysxia%2Fmetamorph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flysxia%2Fmetamorph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flysxia%2Fmetamorph/lists"}