{"id":16348956,"url":"https://github.com/sheaf/if-instance","last_synced_at":"2025-03-21T00:30:30.077Z","repository":{"id":56844538,"uuid":"401420428","full_name":"sheaf/if-instance","owner":"sheaf","description":"GHC plugin to branch on whether a constraint is satisfied","archived":false,"fork":false,"pushed_at":"2024-11-29T12:36:15.000Z","size":44,"stargazers_count":33,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-17T19:01:46.996Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sheaf.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":null,"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":"2021-08-30T16:56:33.000Z","updated_at":"2025-02-24T08:59:28.000Z","dependencies_parsed_at":"2024-10-28T09:24:47.997Z","dependency_job_id":null,"html_url":"https://github.com/sheaf/if-instance","commit_stats":{"total_commits":8,"total_committers":1,"mean_commits":8.0,"dds":0.0,"last_synced_commit":"47bf4c6bfaddc3f9116cf35deba5bf7bfec58f20"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fif-instance","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fif-instance/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fif-instance/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheaf%2Fif-instance/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sheaf","download_url":"https://codeload.github.com/sheaf/if-instance/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244717127,"owners_count":20498280,"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-11T00:56:10.350Z","updated_at":"2025-03-21T00:30:29.658Z","avatar_url":"https://github.com/sheaf.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# if-instance \u003ca href=\"https://hackage.haskell.org/package/if-instance\" alt=\"Hackage\"\u003e\u003cimg src=\"https://img.shields.io/hackage/v/if-instance.svg\" /\u003e\u003c/a\u003e\n\nThis library provides a way to branch on whether a constraint is satisfied:\n\n```haskell\n{-# OPTIONS_GHC -fplugin=IfSat.Plugin #-}\n\nmodule MyModule where\n\nimport Data.Constraint.If ( IfSat, ifSat )\n\nhypot :: forall a. ( Floating a, IfSat (FMA a) ) =\u003e a -\u003e a -\u003e a\nhypot = ifSat @(FMA a) withFMA withoutFMA\n  where\n    withFMA :: FMA a =\u003e a -\u003e a -\u003e a\n    withFMA x y =\n      let\n        h = sqrt $ fma x x (y * y)\n        h² = h * h\n        x² = x * x\n        u = fma (-y) y (h² - x²) + fma h h (-h²) - fma x x (-x²)\n      in\n        h - u / ( 2 * h )\n    withoutFMA :: a -\u003e a -\u003e a\n    withoutFMA x y = sqrt ( x * x + y * y )\n```\n\n`hypot x y` computes the value of `sqrt( x² + y² )` in a different way\ndepending on whether a fused multiply-add operation `fma` is available\nfor the type `a`. This makes use of the `ifSat` function:\n\n```haskell\nifSat :: IfSat c =\u003e ( c =\u003e r ) -\u003e r -\u003e r\n```\n\nwhich chooses the first branch when `c` is satisfied, and uses the second branch\nas a fallback otherwise.\n\n## When does branch selection occur?\n\nWhat is important to understand is that the branch selection happens\nprecisely when we need to solve the `IfSat ct` constraint.\n\n```haskell\n{-# OPTIONS_GHC -fplugin=IfSat.Plugin #-}\nmodule M1 where\n\nshowFun :: forall (a :: Type). IfSat ( Show ( a -\u003e a ) ) =\u003e ( a -\u003e a ) -\u003e String\nshowFun = ifSat @( Show (a -\u003e a) ) show ( \\ _ -\u003e \"\u003c\u003cfunction\u003e\u003e\" )\n\ntest1 :: ( Bool -\u003e Bool ) -\u003e String\ntest1 fun = showFun fun\n\n----------------------------------------\n\n{-# OPTIONS_GHC -fplugin=IfSat.Plugin #-}\nmodule M2 where\n\nimport M1\n\ninstance Show ( Bool -\u003e Bool ) where\n  show f = show [ f False, f True ]\n\ntest2 :: ( a -\u003e a ) -\u003e String\ntest2 fun = showFun fun\n\ntest3 :: ( Bool -\u003e Bool ) -\u003e String\ntest3 fun = showFun fun\n```\n\nAfter loading `M2`, we get the following results:\n\n```haskell\ntest1 not\n```\n\u003e `\"\u003c\u003cfunction\u003e\u003e\"`\n\nIn this example, to typecheck `test1` we need to solve `IfSat (Show (Bool -\u003e Bool))`\ninside module `M1`.  \nAs no instance for `Show (Bool -\u003e Bool)` is available in `M1`, we pick the second branch,\nresulting in `\"\u003c\u003cfunction\u003e\u003e\"`.\n\n```haskell\ntest2 not\n```\n\u003e `\"\u003c\u003cfunction\u003e\u003e\"`\n\nIn this example, we must solve `IfSat (Show (a -\u003e a))` within `M2`.\nThere is no such instance in `M2`, so we pick the second branch.  \nIt doesn't matter that we are calling `test2` with a function of type\n`Bool -\u003e Bool`: we had to solve `IfSat (Show (a -\u003e a))` when type-checking\nthe type signature of `test2`.\n\n```haskell\ntest3 not\n```\n\u003e `\"[True, False]\"`\n\nIn this last example, we must solve `IfSat (Show (Bool -\u003e Bool))`, but as we're in `M2`,\nsuch an instance is available, so we choose the first branch.\n\nNote in particular that `test1` and `test3` have the exact same definition\n(same type signature, same body), but produce a different result.\nThis is because the satisfiability check happens in different contexts.\n\n## Using additional constraints in the fallback branch\n\nIf you need to use additional constraints in the fallback branch, you can instead use the\nconstraint disjunction operator `(||)`, by way of the `dispatch` function:\n\n```haskell\ndispatch :: ( c || d ) =\u003e ( c =\u003e r ) -\u003e ( d =\u003e r ) -\u003e r\n```\n\nThe `dispatch` function will select the `c =\u003e r` branch when `c` is satisfied,\nand otherwise fall back to the `d =\u003e r` branch.  \nAs with `ifSat`, the selection occurs precisely when the `c || d` constraint is solved.\n\nThis functionality is strictly more general, as `IfSat ct` is the special case in which\nthe second constraint is trivially satisfied (using the trivial constraint `() :: Constraint`):\n\n```haskell\ntype IfSat ct = ( ct || () )\n\nifSat :: forall ct r. IfSat ct =\u003e ( c =\u003e r ) -\u003e r -\u003e r\nifSat f g = dispatch @ct @() f g\n```\n\n## A type-family too!\n\nIf you prefer working at the type-level, this library has got you covered, with the `IsSat` type family.  \nTo reduce `IsSat ct`, GHC will first attempt to solve `ct`. If it succeeds, then `IsSat ct` reduces to `True`;\notherwise, it reduces to `False`. This means that the satisfiability check is performed precisely at the time of type-family reduction.\n\nThis is clearly quite dangerous: the type family application `IsSat ct` might evaluate to `False` in a certain context,\nbut then in another module when more instances become available it will switch to evaluating to `True`.  \nThis allows us to unsafely coerce between any two types:\n\n```haskell\nmodule M1 where\n\ntype F :: Bool -\u003e Type\ntype family F b where\n  F False = Float\n  F True  = Text\n\nclass C\n\nfoo :: Float -\u003e F (IsSat C)\nfoo x = x\n\n----------------------------------------\n\nmodule M2 where\n\nimport M1\n\ninstance C\n\nunsafeCoerce :: Float -\u003e Text\nunsafeCoerce x = foo x\n```\n\nTo avoid such issues, it's recommended to restrict usage of `IsSat` to internal constraints,\ne.g. a typeclass that is defined internally in a library and isn't exported.\n\n# Doesn't this library already exist?\n\nYes. Mike Izbicki's [`ifCxt` library](https://github.com/mikeizbicki/ifcxt) inspired this library,\nwith constraint disjunction `(||)` taken from Noah Luck Easterly's [`constraint-unions`](https://github.com/rampion/constraint-unions).\n\nWhat's the difference? The above libraries require users to manually declare instances\nfor the typeclasses they want to work with, e.g. by using Template Haskell.  \nOn the other hand, this library only requires users to enable the plugin,\nwhich directly hooks into GHC to solve `c || d` and `IfSat c` constraints,\nwithout requiring large amounts of instances to be defined by hand.  \nThis also means that users have more flexibility: as we saw above, branch selection occurs\nwhen the `c || d` or `IfSat c` constraints are discharged, looking at all the information\nthat is available at that point. This includes instance declarations,\nGiven constraints, local evidence (e.g. from GADT pattern matches), etc.\n\nFurthermore, this library isn't limited to working with typeclasses and their instances:\nany constraint can be passed to `IfSat`, e.g. an equality constraint involving a type family,\nwhich might only be satisfied in the presence of further type-family equations.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Fif-instance","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheaf%2Fif-instance","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheaf%2Fif-instance/lists"}