mirror of
https://github.com/tfausak/witch.git
synced 2024-11-26 19:33:37 +03:00
Merge pull request #5 from tfausak/cauldron
Split things up and pare them down
This commit is contained in:
commit
300f723838
5
.devcontainer/Dockerfile
Normal file
5
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM haskell:9.0.1
|
||||||
|
ARG USER=haskell
|
||||||
|
RUN useradd --create-home --shell "$( which bash )" "$USER"
|
||||||
|
USER "$USER"
|
||||||
|
ENV PATH="/home/$USER/.cabal/bin:$PATH"
|
9
.devcontainer/devcontainer.json
Normal file
9
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"taylorfausak.purple-yolk"
|
||||||
|
],
|
||||||
|
"postCreateCommand": "cabal update"
|
||||||
|
}
|
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
@ -12,13 +12,14 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: ubuntu-18.04, ghc: 9.0.1, cabal: 3.4.0.0 }
|
- { os: ubuntu-20.04, ghc: 9.0.1, cabal: 3.4.0.0 }
|
||||||
- { os: ubuntu-18.04, ghc: 8.10.3, cabal: 3.2.0.0 }
|
- { os: macos-10.15, ghc: 9.0.1, cabal: 3.4.0.0 }
|
||||||
- { os: macos-10.15, ghc: 8.10.3, cabal: 3.2.0.0 }
|
- { os: windows-2019, ghc: 9.0.1, cabal: 3.4.0.0 }
|
||||||
- { os: windows-2019, ghc: 8.10.3, cabal: 3.2.0.0 }
|
- { os: ubuntu-20.04, ghc: 8.10.4, cabal: 3.2.1.0 }
|
||||||
- { os: ubuntu-18.04, ghc: 8.8.4, cabal: 3.0.0.0 }
|
- { os: ubuntu-20.04, ghc: 8.8.4, cabal: 3.0.1.0 }
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -42,5 +43,5 @@ jobs:
|
|||||||
path: dist-newstyle/sdist/witch-*.tar.gz
|
path: dist-newstyle/sdist/witch-*.tar.gz
|
||||||
name: witch-${{ github.sha }}.tar.gz
|
name: witch-${{ github.sha }}.tar.gz
|
||||||
- run: cabal check
|
- run: cabal check
|
||||||
- if: github.event_name == 'release' && matrix.os == 'ubuntu-18.04' && matrix.ghc == '9.0.1'
|
- if: github.event_name == 'release' && matrix.os == 'ubuntu-20.04' && matrix.ghc == '9.0.1'
|
||||||
run: cabal upload --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' dist-newstyle/sdist/witch-*.tar.gz
|
run: cabal upload --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' dist-newstyle/sdist/witch-*.tar.gz
|
||||||
|
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"purple-yolk.brittany.command": "false",
|
||||||
|
"purple-yolk.ghci.command": "cabal repl --ghc-options -Wwarn --repl-options -ddump-json",
|
||||||
|
"purple-yolk.hlint.command": "false",
|
||||||
|
"purple-yolk.hlint.onSave": false,
|
||||||
|
"editor.rulers": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimFinalNewlines": true
|
||||||
|
}
|
@ -5,3 +5,7 @@
|
|||||||
[![Stackage](https://www.stackage.org/package/witch/badge/nightly?label=stackage)](https://www.stackage.org/package/witch)
|
[![Stackage](https://www.stackage.org/package/witch/badge/nightly?label=stackage)](https://www.stackage.org/package/witch)
|
||||||
|
|
||||||
:mage_woman: Convert values from one type into another.
|
:mage_woman: Convert values from one type into another.
|
||||||
|
|
||||||
|
## To do
|
||||||
|
|
||||||
|
- [ ] Add more instances ...
|
||||||
|
6
cabal.project
Normal file
6
cabal.project
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
jobs: $ncpus
|
||||||
|
packages: .
|
||||||
|
|
||||||
|
package witch
|
||||||
|
ghc-options: -Werror
|
||||||
|
tests: True
|
73
src/ghc-8.10/Witch/Lift.hs
Normal file
73
src/ghc-8.10/Witch/Lift.hs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Witch.Lift where
|
||||||
|
|
||||||
|
import qualified Data.Typeable as Typeable
|
||||||
|
import qualified Language.Haskell.TH.Syntax as TH
|
||||||
|
import qualified Witch.Identity as Identity
|
||||||
|
import qualified Witch.TryCast as TryCast
|
||||||
|
import qualified Witch.Utility as Utility
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeCast' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeCast "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast "some literal")
|
||||||
|
liftedCast
|
||||||
|
:: forall source target
|
||||||
|
. ( TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedCast = TH.liftTyped . Utility.unsafeCast
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeFrom' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeFrom @s "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @s "some literal")
|
||||||
|
liftedFrom
|
||||||
|
:: forall s target source
|
||||||
|
. ( Identity.Identity s ~ source
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedFrom = liftedCast
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeInto' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeInto @t "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @t "some literal")
|
||||||
|
liftedInto
|
||||||
|
:: forall t source target
|
||||||
|
. ( Identity.Identity t ~ target
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedInto = liftedCast
|
73
src/ghc-8.8/Witch/Lift.hs
Normal file
73
src/ghc-8.8/Witch/Lift.hs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Witch.Lift where
|
||||||
|
|
||||||
|
import qualified Data.Typeable as Typeable
|
||||||
|
import qualified Language.Haskell.TH.Syntax as TH
|
||||||
|
import qualified Witch.Identity as Identity
|
||||||
|
import qualified Witch.TryCast as TryCast
|
||||||
|
import qualified Witch.Utility as Utility
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeCast' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeCast "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast "some literal")
|
||||||
|
liftedCast
|
||||||
|
:: forall source target
|
||||||
|
. ( TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedCast s = TH.unsafeTExpCoerce $ TH.lift (Utility.unsafeCast s :: target)
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeFrom' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeFrom @s "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @s "some literal")
|
||||||
|
liftedFrom
|
||||||
|
:: forall s target source
|
||||||
|
. ( Identity.Identity s ~ source
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedFrom = liftedCast
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeInto' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeInto @t "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @t "some literal")
|
||||||
|
liftedInto
|
||||||
|
:: forall t source target
|
||||||
|
. ( Identity.Identity t ~ target
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Q (TH.TExp target)
|
||||||
|
liftedInto = liftedCast
|
76
src/ghc-9.0/Witch/Lift.hs
Normal file
76
src/ghc-9.0/Witch/Lift.hs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Witch.Lift where
|
||||||
|
|
||||||
|
import qualified Data.Typeable as Typeable
|
||||||
|
import qualified Language.Haskell.TH.Syntax as TH
|
||||||
|
import qualified Witch.Identity as Identity
|
||||||
|
import qualified Witch.TryCast as TryCast
|
||||||
|
import qualified Witch.Utility as Utility
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeCast' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeCast "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast "some literal")
|
||||||
|
liftedCast
|
||||||
|
:: forall source target m
|
||||||
|
. ( TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
, TH.Quote m
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Code m target
|
||||||
|
liftedCast = TH.liftTyped . Utility.unsafeCast
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeFrom' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeFrom @s "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @s "some literal")
|
||||||
|
liftedFrom
|
||||||
|
:: forall s target m source
|
||||||
|
. ( Identity.Identity s ~ source
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
, TH.Quote m
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Code m target
|
||||||
|
liftedFrom = liftedCast
|
||||||
|
|
||||||
|
-- | This is like 'Utility.unsafeInto' except that it works at compile time
|
||||||
|
-- rather than runtime.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > unsafeInto @t "some literal"
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > $$(liftedCast @t "some literal")
|
||||||
|
liftedInto
|
||||||
|
:: forall t source m target
|
||||||
|
. ( Identity.Identity t ~ target
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, TH.Lift target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
, TH.Quote m
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> TH.Code m target
|
||||||
|
liftedInto = liftedCast
|
430
src/lib/Witch.hs
430
src/lib/Witch.hs
@ -1,319 +1,161 @@
|
|||||||
{-# language AllowAmbiguousTypes #-}
|
-- | The Witch package is a library that allows you to confidently convert
|
||||||
{-# language DefaultSignatures #-}
|
-- values between various types. This module exports everything you need to
|
||||||
{-# language FlexibleInstances #-}
|
-- perform conversions or define your own. It is designed to be imported
|
||||||
{-# language MultiParamTypeClasses #-}
|
-- unqualified, so getting started is as easy as:
|
||||||
{-# language ScopedTypeVariables #-}
|
|
||||||
{-# language TypeFamilies #-}
|
|
||||||
|
|
||||||
-- | This module provides the 'Cast' type class for converting values between
|
|
||||||
-- various types. This aims to be a common interface for the various @xToY@ or
|
|
||||||
-- @yFromX@ functions you might write instead. It is inspired by the
|
|
||||||
-- @std::convert::From@ trait that the Rust programming language provides.
|
|
||||||
--
|
--
|
||||||
-- Many Haskell libraries already provide similar functionality. Here's how
|
-- >>> import Witch
|
||||||
-- this module compares to them:
|
module Witch
|
||||||
|
( -- * Motivation
|
||||||
|
-- | Haskell provides many ways to convert between common types, and core
|
||||||
|
-- libraries add even more. It can be challenging to know which function to
|
||||||
|
-- use when converting from some source type @a@ to some target type @b@. It
|
||||||
|
-- can be even harder to know if that conversion is safe or if there are any
|
||||||
|
-- pitfalls to watch out for.
|
||||||
--
|
--
|
||||||
-- - <https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Coerce.html>:
|
-- This library tries to address that problem by providing a common
|
||||||
-- This type class is convenient because it's automatically inferred by the
|
-- interface for converting between types. The 'Witch.Cast.Cast' type class
|
||||||
-- compiler, but it only works for types that have the same runtime
|
-- is for conversions that cannot fail, and the 'Witch.TryCast.TryCast' type
|
||||||
-- representation.
|
-- class is for conversions that can fail. These type classes are inspired
|
||||||
|
-- by the [@From@](https://doc.rust-lang.org/std/convert/trait.From.html)
|
||||||
|
-- trait in Rust.
|
||||||
|
|
||||||
|
-- * Alternatives
|
||||||
|
-- | Many Haskell libraries already provide similar functionality. How is
|
||||||
|
-- this library different?
|
||||||
--
|
--
|
||||||
-- - <https://hackage.haskell.org/package/convertible-1.1.1.0/docs/Data-Convertible-Base.html>:
|
-- - [@Coercible@](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Coerce.html#t:Coercible):
|
||||||
-- This type class allows for conversions to fail.
|
-- This type class is solved by the compiler, but it only works for types
|
||||||
|
-- that have the same runtime representation. This is very convenient for
|
||||||
|
-- @newtype@s, but it does not work for converting between arbitrary types
|
||||||
|
-- like @Int8@ and @Int16@.
|
||||||
--
|
--
|
||||||
-- - <https://hackage.haskell.org/package/basement-0.0.11/docs/Basement-From.html>:
|
-- - [@Convertible@](https://hackage.haskell.org/package/convertible-1.1.1.0/docs/Data-Convertible-Base.html#t:Convertible):
|
||||||
-- This type class is essentially the same, but the @basement@ package is an
|
-- This popular conversion type class is similar to what this library
|
||||||
-- alternative standard library that some people may not want to depend on.
|
-- provides. The main difference is that it does not differentiate between
|
||||||
|
-- conversions that can fail and those that cannot.
|
||||||
--
|
--
|
||||||
-- - <https://hackage.haskell.org/package/inj-base-0.2.0.0/docs/Inj-Base.html>:
|
-- - [@From@](https://hackage.haskell.org/package/basement-0.0.11/docs/Basement-From.html#t:From):
|
||||||
-- This type class requires conversions to be injective, as opposed to merely
|
-- This type class is almost identical to what this library provides.
|
||||||
-- suggesting it. Also some conversions fail at runtime.
|
-- Unfortunately it is part of the @basement@ package, which is an
|
||||||
|
-- alternative standard library that some people may not want to depend
|
||||||
|
-- on.
|
||||||
--
|
--
|
||||||
-- - <https://github.com/mbj/conversions/blob/6ac6c52/src/Data/Conversions/FromType.hs>:
|
-- - [@Inj@](https://hackage.haskell.org/package/inj-1.0/docs/Inj.html#t:Inj):
|
||||||
-- This type class comes with many convenient helper functions, but some of
|
-- This type class requires instances to be an injection, which means that
|
||||||
-- the provided instances fail at runtime.
|
-- no two input values should map to the same output. That restriction
|
||||||
|
-- prohibits many useful instances. Also many instances throw impure
|
||||||
|
-- exceptions.
|
||||||
--
|
--
|
||||||
-- - <https://github.com/kframework/kore/blob/626f230/kore/src/From.hs>:
|
-- In addition to those general-purpose type classes, there are many
|
||||||
-- This package is not available on Hackage, but otherwise is very similar to
|
-- alternatives for more specific conversions. How does this library compare
|
||||||
-- this one.
|
-- to those?
|
||||||
module Witch (Cast(cast), from, into, via) where
|
|
||||||
|
|
||||||
import qualified Data.ByteString as ByteString
|
|
||||||
import qualified Data.ByteString.Lazy as LazyByteString
|
|
||||||
import qualified Data.Coerce as Coerce
|
|
||||||
import qualified Data.Foldable as Foldable
|
|
||||||
import qualified Data.Int as Int
|
|
||||||
import qualified Data.List.NonEmpty as NonEmpty
|
|
||||||
import qualified Data.Map as Map
|
|
||||||
import qualified Data.Sequence as Seq
|
|
||||||
import qualified Data.Set as Set
|
|
||||||
import qualified Data.Text as Text
|
|
||||||
import qualified Data.Text.Lazy as LazyText
|
|
||||||
import qualified Data.Tuple as Tuple
|
|
||||||
import qualified Data.Void as Void
|
|
||||||
import qualified Data.Word as Word
|
|
||||||
import qualified Numeric.Natural as Natural
|
|
||||||
|
|
||||||
-- | This type class represents a way to convert values from some type into
|
|
||||||
-- another type. The constraint @Cast a b@ means that you can convert from a
|
|
||||||
-- value of type @a@ into a value of type @b@.
|
|
||||||
--
|
--
|
||||||
-- This is primarily intended for "zero cost" conversions like @newtype@s. For
|
-- - Monomorphic conversion functions like [@Data.Text.pack@](https://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:pack)
|
||||||
-- example if you wanted to have a type to represent someone's name, you could
|
-- are explicit but not necessarily convenient. It can be tedious to
|
||||||
-- say:
|
-- manage the imports necessary to use the functions. And if you want to
|
||||||
|
-- put them in a custom prelude, you will have to come up with your own
|
||||||
|
-- names.
|
||||||
--
|
--
|
||||||
-- > newtype Name = Name String
|
-- - Polymorphic conversion methods like 'toEnum' are more convenient but
|
||||||
-- > instance Cast String Name
|
-- may have unwanted semantics or runtime behavior. For example the 'Enum'
|
||||||
-- > instance Cast Name String
|
-- type class is more or less tied to the 'Int' data type and frequently
|
||||||
|
-- throws impure exceptions.
|
||||||
--
|
--
|
||||||
-- And then you could convert back and forth between @Name@s and @String@s:
|
-- - Polymorphic conversion functions like 'fromIntegral' are very
|
||||||
|
-- convenient. Unfortunately it can be challenging to know which types
|
||||||
|
-- have the instances necessary to make the conversion possible. And even
|
||||||
|
-- if the conversion is possible, is it safe? For example converting a
|
||||||
|
-- negative 'Int' into a 'Word' will overflow, which may be surprising.
|
||||||
|
|
||||||
|
-- * Instances
|
||||||
|
-- | When should you add a 'Witch.Cast.Cast' (or 'Witch.TryCast.TryCast')
|
||||||
|
-- instance for some pair of types? This is a surprisingly tricky question
|
||||||
|
-- to answer precisely. Instances are driven more by guidelines than rules.
|
||||||
--
|
--
|
||||||
-- > let someString = "Taylor"
|
-- - Conversions must not throw impure exceptions. This means no 'undefined'
|
||||||
-- > let someName = Name someString
|
-- or anything equivalent to it.
|
||||||
-- > into @Name someString -- convert from string to name
|
|
||||||
-- > into @String someName -- convert from name to string
|
|
||||||
--
|
--
|
||||||
-- This type class does not have any laws, but it does have some expectations:
|
-- - Conversions should be unambiguous. If there are multiple reasonable
|
||||||
|
-- ways to convert from @a@ to @b@, then you probably should not add a
|
||||||
|
-- @Cast@ instance for them.
|
||||||
--
|
--
|
||||||
-- - Conversions should be total. A conversion should never fail or crash.
|
-- - Conversions should be lossless. If you have @Cast a b@ then no two @a@
|
||||||
-- Avoid writing instances like @Cast Int (Maybe Char)@. (It might be
|
-- values should be converted to the same @b@ value.
|
||||||
-- worthwhile to have a separate @TryCast@ type class for this.)
|
|
||||||
--
|
--
|
||||||
-- - Conversions should be unambiguous. For example there are many ways to
|
-- - If you have both @Cast a b@ and @Cast b a@, then
|
||||||
-- decode a @ByteString@ into @Text@, so you shouldn't provide an instance
|
-- @cast \@b \@a . cast \@a \@b@ should be the same as 'id'. In other
|
||||||
-- for that.
|
-- words, @a@ and @b@ are isomorphic.
|
||||||
--
|
--
|
||||||
-- - Conversions should be cheap, ideally free. For example converting from
|
-- - If you have both @Cast a b@ and @Cast b c@, then you could also have
|
||||||
-- @String@ to @Text@ is probably fine, but converting from a UTF-8 encoded
|
-- @Cast a c@ and it should be the same as @cast \@b \@c . cast \@a \@b@.
|
||||||
-- @ByteString@ to @Text@ is problematic.
|
-- In other words, @Cast@ is transitive.
|
||||||
--
|
--
|
||||||
-- - Conversions should be lossless. In other words if you have @Cast a b@ then
|
-- In general if @s@ is a @t@, then you should add a 'Witch.Cast.Cast'
|
||||||
-- no two @a@ values should be converted to the same @b@ value. For example
|
-- instance for it. But if @s@ merely can be a @t@, then you could add a
|
||||||
-- @Cast Int Integer@ is fine because every @Int@ can be mapped to a
|
-- 'Witch.TryCast.TryCast' instance for it. And if it is technically
|
||||||
-- corresponding @Integer@, but @Cast Integer Int@ is not good because some
|
-- possible to convert from @s@ to @t@ but there are a lot of caveats, you
|
||||||
-- @Integer@s are out of bounds and would need to be clamped.
|
-- probably should not write any instances at all.
|
||||||
|
|
||||||
|
-- * Type applications
|
||||||
|
-- | This library is designed to be used with the [@TypeApplications@](https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/exts/type_applications.html)
|
||||||
|
-- language extension. Although it is not required for basic functionality,
|
||||||
|
-- it is strongly encouraged. You can use 'Witch.Cast.cast',
|
||||||
|
-- 'Witch.TryCast.tryCast', 'Witch.Utility.unsafeCast', and
|
||||||
|
-- 'Witch.Lift.liftedCast' without type applications. Everything else
|
||||||
|
-- requires a type application.
|
||||||
|
|
||||||
|
-- * Ambiguous types
|
||||||
|
-- | You may see @Identity@ show up in some type signatures. Anywhere you see
|
||||||
|
-- @Identity a@, you can mentally replace it with @a@. It is a type family
|
||||||
|
-- used to trick GHC into requiring type applications for certain functions.
|
||||||
|
-- If you forget to give a type application, you will see an error like
|
||||||
|
-- this:
|
||||||
--
|
--
|
||||||
-- - If you have both @Cast a b@ and @Cast b a@, then @cast . cast@ should be
|
-- >>> from (1 :: Int8) :: Int16
|
||||||
-- the same as 'id'. In other words @a@ and @b@ are isomorphic.
|
-- <interactive>:1:1: error:
|
||||||
|
-- * Couldn't match type `Identity s0' with `Int8'
|
||||||
|
-- arising from a use of `from'
|
||||||
|
-- The type variable `s0' is ambiguous
|
||||||
|
-- * In the expression: from (1 :: Int8) :: Int16
|
||||||
|
-- In an equation for `it': it = from (1 :: Int8) :: Int16
|
||||||
--
|
--
|
||||||
-- - If you have both @Cast a b@ and @Cast b c@, then it's up to you if you
|
-- You can fix the problem by giving a type application:
|
||||||
-- want to provide @Cast a c@. Sometimes using 'via' is ergonomic enough,
|
|
||||||
-- other times you want the extra instance. (It would be nice if we could
|
|
||||||
-- provide @instance (Cast a b, Cast b c) => Cast a c where cast = via \@b@.)
|
|
||||||
class Cast source target where
|
|
||||||
-- | This method implements the conversion of a value between types. In
|
|
||||||
-- practice most instances don't need an explicit implementation. At call
|
|
||||||
-- sites you'll usually want to use 'from' or 'into' instead of 'cast'.
|
|
||||||
--
|
--
|
||||||
-- The default implementation of 'cast' simply calls 'Coerce.coerce', which
|
-- >>> from @Int8 1 :: Int16
|
||||||
-- works for types that have the same runtime representation.
|
-- 1
|
||||||
cast :: source -> target
|
|
||||||
default cast :: Coerce.Coercible source target => source -> target
|
|
||||||
cast = Coerce.coerce
|
|
||||||
|
|
||||||
-- This ugly hack is used to require type applications when calling 'from' and
|
-- * Type classes
|
||||||
-- 'into'. See <https://twitter.com/taylorfausak/status/1329084033003782148>.
|
-- ** Cast
|
||||||
data Never
|
Witch.Cast.Cast(cast)
|
||||||
type family Ambiguous a where
|
, Witch.Utility.from
|
||||||
Ambiguous Never = ()
|
, Witch.Utility.into
|
||||||
Ambiguous a = a
|
|
||||||
|
|
||||||
-- | This function converts a value from one type into another. This is
|
-- ** TryCast
|
||||||
-- intended to be used with the @TypeApplications@ language extension. The
|
, Witch.TryCast.TryCast(tryCast)
|
||||||
-- @Ambiguous@ type in the signature makes a type application required. If
|
, Witch.Utility.tryFrom
|
||||||
-- you'd prefer not to provide a type application, use 'cast' instead.
|
, Witch.Utility.tryInto
|
||||||
--
|
, Witch.TryCastException.TryCastException(..)
|
||||||
-- As an example, here are a few ways to convert from an 'Int' into an
|
|
||||||
-- 'Integer':
|
|
||||||
--
|
|
||||||
-- > from @Int @Integer 123
|
|
||||||
-- > from @_ @Integer (123 :: Int)
|
|
||||||
-- > from @Int @_ 123 :: Integer
|
|
||||||
-- > from @Int 123 :: Integer
|
|
||||||
--
|
|
||||||
-- Often the context around an expression will make the explicit type
|
|
||||||
-- signatures unnecessary. If you find yourself using a partial type
|
|
||||||
-- signature, consider using 'into' instead. For example:
|
|
||||||
--
|
|
||||||
-- > let someInt = 123 :: Int
|
|
||||||
-- > from @_ @Integer someInt -- avoid this
|
|
||||||
-- > into @Integer someInt -- prefer this
|
|
||||||
--
|
|
||||||
from :: forall s target source . (Ambiguous s ~ source, Cast source target) => source -> target
|
|
||||||
from = cast
|
|
||||||
|
|
||||||
-- | This function converts a value from one type into another. This is the
|
-- * Utilities
|
||||||
-- same as 'from' except that the type variables are in the opposite order.
|
, Witch.Utility.as
|
||||||
into :: forall t source target . (Ambiguous t ~ target, Cast source target) => source -> target
|
, Witch.Utility.over
|
||||||
into = cast
|
, Witch.Utility.via
|
||||||
|
, Witch.Utility.tryVia
|
||||||
|
|
||||||
-- | This function converts a value from one type into another by going through
|
-- ** Unsafe
|
||||||
-- some third type. This is the same as calling 'cast' (or 'from' or 'into')
|
, Witch.Utility.unsafeCast
|
||||||
-- twice, but can sometimes be more convenient.
|
, Witch.Utility.unsafeFrom
|
||||||
--
|
, Witch.Utility.unsafeInto
|
||||||
-- Note that the type in the middle of the conversion is the first type
|
|
||||||
-- variable of this function. In other words, @via \@b \@a \@c@ first converts
|
|
||||||
-- from @a@ to @b@, and then from @b@ to @c@. Often both @a@ and @c@ will be
|
|
||||||
-- inferred from context, which means you can just write @via \@b@.
|
|
||||||
via :: forall through source target . (Cast source through, Cast through target) => source -> target
|
|
||||||
via = cast . (\ x -> x :: through) . cast
|
|
||||||
|
|
||||||
-- | 'id'
|
-- ** Template Haskell
|
||||||
instance Cast a a where
|
, Witch.Lift.liftedCast
|
||||||
cast = id
|
, Witch.Lift.liftedFrom
|
||||||
|
, Witch.Lift.liftedInto
|
||||||
|
) where
|
||||||
|
|
||||||
-- | 'const'
|
import qualified Witch.Cast
|
||||||
instance Cast a (x -> a) where
|
import Witch.Instances ()
|
||||||
cast = const
|
import qualified Witch.Lift
|
||||||
|
import qualified Witch.TryCast
|
||||||
-- | 'pure'
|
import qualified Witch.TryCastException
|
||||||
instance Cast a [a] where
|
import qualified Witch.Utility
|
||||||
cast = pure
|
|
||||||
|
|
||||||
-- | 'Just'
|
|
||||||
instance Cast a (Maybe a) where
|
|
||||||
cast = Just
|
|
||||||
|
|
||||||
-- | 'Left'
|
|
||||||
instance Cast a (Either a x) where
|
|
||||||
cast = Left
|
|
||||||
|
|
||||||
-- | 'Right'
|
|
||||||
instance Cast a (Either x a) where
|
|
||||||
cast = Right
|
|
||||||
|
|
||||||
-- | 'Void.absurd'
|
|
||||||
instance Cast Void.Void x where
|
|
||||||
cast = Void.absurd
|
|
||||||
|
|
||||||
-- | 'fst'
|
|
||||||
instance Cast (a, x) a where
|
|
||||||
cast = fst
|
|
||||||
|
|
||||||
-- | 'snd'
|
|
||||||
instance Cast (x, a) a where
|
|
||||||
cast = snd
|
|
||||||
|
|
||||||
-- | 'Tuple.swap'
|
|
||||||
instance Cast (a, b) (b, a) where
|
|
||||||
cast = Tuple.swap
|
|
||||||
|
|
||||||
-- | 'NonEmpty.toList'
|
|
||||||
instance Cast (NonEmpty.NonEmpty a) [a] where
|
|
||||||
cast = NonEmpty.toList
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Word.Word8 Word.Word16 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Word.Word16 Word.Word32 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Word.Word32 Word.Word64 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Word Natural.Natural where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Natural.Natural Integer where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Int.Int8 Int.Int16 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Int.Int16 Int.Int32 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Int.Int32 Int.Int64 where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Int Integer where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'fromIntegral'
|
|
||||||
instance Cast Integer Rational where
|
|
||||||
cast = fromIntegral
|
|
||||||
|
|
||||||
-- | 'realToFrac'
|
|
||||||
instance Cast Float Double where
|
|
||||||
cast = realToFrac
|
|
||||||
|
|
||||||
-- | 'fromEnum'
|
|
||||||
instance Cast Bool Int where
|
|
||||||
cast = fromEnum
|
|
||||||
|
|
||||||
-- | 'fromEnum'
|
|
||||||
instance Cast Char Int where
|
|
||||||
cast = fromEnum
|
|
||||||
|
|
||||||
-- | 'ByteString.pack'
|
|
||||||
instance Cast [Word.Word8] ByteString.ByteString where
|
|
||||||
cast = ByteString.pack
|
|
||||||
|
|
||||||
-- | 'ByteString.unpack'
|
|
||||||
instance Cast ByteString.ByteString [Word.Word8] where
|
|
||||||
cast = ByteString.unpack
|
|
||||||
|
|
||||||
-- | 'LazyByteString.fromStrict'
|
|
||||||
instance Cast ByteString.ByteString LazyByteString.ByteString where
|
|
||||||
cast = LazyByteString.fromStrict
|
|
||||||
|
|
||||||
-- | 'LazyByteString.toStrict'
|
|
||||||
instance Cast LazyByteString.ByteString ByteString.ByteString where
|
|
||||||
cast = LazyByteString.toStrict
|
|
||||||
|
|
||||||
-- | 'Text.pack'
|
|
||||||
--
|
|
||||||
-- Note that some 'Char' values cannot be represented in 'Text' and will be
|
|
||||||
-- replaced by U+FFFD.
|
|
||||||
instance Cast String Text.Text where
|
|
||||||
cast = Text.pack
|
|
||||||
|
|
||||||
-- | 'Text.unpack'
|
|
||||||
instance Cast Text.Text String where
|
|
||||||
cast = Text.unpack
|
|
||||||
|
|
||||||
-- | 'LazyText.fromStrict'
|
|
||||||
instance Cast Text.Text LazyText.Text where
|
|
||||||
cast = LazyText.fromStrict
|
|
||||||
|
|
||||||
-- | 'LazyText.toStrict'
|
|
||||||
instance Cast LazyText.Text Text.Text where
|
|
||||||
cast = LazyText.toStrict
|
|
||||||
|
|
||||||
-- | 'Seq.fromList'
|
|
||||||
instance Cast [a] (Seq.Seq a) where
|
|
||||||
cast = Seq.fromList
|
|
||||||
|
|
||||||
-- | 'Foldable.toList'
|
|
||||||
instance Cast (Seq.Seq a) [a] where
|
|
||||||
cast = Foldable.toList
|
|
||||||
|
|
||||||
-- | 'Set.fromList'
|
|
||||||
--
|
|
||||||
-- Note that this will remove duplicate elements from the list.
|
|
||||||
instance Ord a => Cast [a] (Set.Set a) where
|
|
||||||
cast = Set.fromList
|
|
||||||
|
|
||||||
-- | 'Set.toAscList'
|
|
||||||
instance Cast (Set.Set a) [a] where
|
|
||||||
cast = Set.toAscList
|
|
||||||
|
|
||||||
-- | 'Map.fromList'
|
|
||||||
--
|
|
||||||
-- Note that if there are duplicate keys in the list, the one closest to the
|
|
||||||
-- end will win.
|
|
||||||
instance Ord k => Cast [(k, v)] (Map.Map k v) where
|
|
||||||
cast = Map.fromList
|
|
||||||
|
|
||||||
-- | 'Map.toAscList'
|
|
||||||
instance Cast (Map.Map k v) [(k, v)] where
|
|
||||||
cast = Map.toAscList
|
|
||||||
|
31
src/lib/Witch/Cast.hs
Normal file
31
src/lib/Witch/Cast.hs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{-# LANGUAGE DefaultSignatures #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
|
||||||
|
module Witch.Cast where
|
||||||
|
|
||||||
|
import qualified Data.Coerce as Coerce
|
||||||
|
|
||||||
|
-- | This type class is for converting values from some @source@ type into
|
||||||
|
-- some other @target@ type. The constraint @Cast source target@ measn that
|
||||||
|
-- you can convert from a value of type @source@ into a value of type
|
||||||
|
-- @target@.
|
||||||
|
--
|
||||||
|
-- This type class is for conversions that cannot fail. If your conversion can
|
||||||
|
-- fail, consider implementing @TryCast@ instead.
|
||||||
|
class Cast source target where
|
||||||
|
-- | This method implements the conversion of a value between types. At call
|
||||||
|
-- sites you will usually want to use @from@ or @into@ instead of this
|
||||||
|
-- method.
|
||||||
|
--
|
||||||
|
-- The default implementation of this method simply calls 'Coerce.coerce',
|
||||||
|
-- which works for types that have the same runtime representation. This
|
||||||
|
-- means that for @newtype@s you do not need to implement this method at
|
||||||
|
-- all. For example:
|
||||||
|
--
|
||||||
|
-- >>> newtype Name = Name String
|
||||||
|
-- >>> instance Cast Name String
|
||||||
|
-- >>> instance Cast String Name
|
||||||
|
cast :: source -> target
|
||||||
|
|
||||||
|
default cast :: Coerce.Coercible source target => source -> target
|
||||||
|
cast = Coerce.coerce
|
14
src/lib/Witch/Identity.hs
Normal file
14
src/lib/Witch/Identity.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Witch.Identity where
|
||||||
|
|
||||||
|
-- | This is an ugly hack used to make GHC require type applications for
|
||||||
|
-- certain functions. See this Twitter thread for a discussion:
|
||||||
|
-- <https://twitter.com/taylorfausak/status/1329084033003782148>.
|
||||||
|
type family Identity a where
|
||||||
|
Identity Never = ()
|
||||||
|
Identity a = a
|
||||||
|
|
||||||
|
-- | Never use this type for anything! It only exists to make the 'Identity'
|
||||||
|
-- type family non-trivial.
|
||||||
|
data Never
|
1073
src/lib/Witch/Instances.hs
Normal file
1073
src/lib/Witch/Instances.hs
Normal file
File diff suppressed because it is too large
Load Diff
18
src/lib/Witch/TryCast.hs
Normal file
18
src/lib/Witch/TryCast.hs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
|
||||||
|
module Witch.TryCast where
|
||||||
|
|
||||||
|
import qualified Witch.TryCastException as TryCastException
|
||||||
|
|
||||||
|
-- | This type class is for converting values from some @source@ type into
|
||||||
|
-- some other @target@ type. The constraint @TryCast source target@ means that
|
||||||
|
-- you may be able to convert from a value of type @source@ into a value of
|
||||||
|
-- type @target@, but that conversion may fail at runtime.
|
||||||
|
--
|
||||||
|
-- This type class is for conversions that can fail. If your conversion cannot
|
||||||
|
-- fail, consider implementing @Cast@ instead.
|
||||||
|
class TryCast source target where
|
||||||
|
-- | This method implements the conversion of a value between types. At call
|
||||||
|
-- sites you will usually want to use @tryFrom@ or @tryInto@ instead of this
|
||||||
|
-- method.
|
||||||
|
tryCast :: source -> Either (TryCastException.TryCastException source target) target
|
33
src/lib/Witch/TryCastException.hs
Normal file
33
src/lib/Witch/TryCastException.hs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Witch.TryCastException where
|
||||||
|
|
||||||
|
import qualified Control.Exception as Exception
|
||||||
|
import qualified Data.Proxy as Proxy
|
||||||
|
import qualified Data.Typeable as Typeable
|
||||||
|
|
||||||
|
-- | This exception is thrown when a @TryCast@ conversion fails. It has the
|
||||||
|
-- original @source@ value that caused the failure and it knows the @target@
|
||||||
|
-- type it was trying to convert into.
|
||||||
|
newtype TryCastException source target
|
||||||
|
= TryCastException source
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
instance
|
||||||
|
( Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
) => Show (TryCastException source target) where
|
||||||
|
showsPrec d (TryCastException x) =
|
||||||
|
showParen (d > 10)
|
||||||
|
$ showString "TryCastException {- "
|
||||||
|
. shows
|
||||||
|
(Typeable.typeRep (Proxy.Proxy :: Proxy.Proxy (source -> target)))
|
||||||
|
. showString " -} "
|
||||||
|
. showsPrec 11 x
|
||||||
|
|
||||||
|
instance
|
||||||
|
( Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
) => Exception.Exception (TryCastException source target)
|
211
src/lib/Witch/Utility.hs
Normal file
211
src/lib/Witch/Utility.hs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Witch.Utility where
|
||||||
|
|
||||||
|
import qualified Control.Exception as Exception
|
||||||
|
import qualified Data.Typeable as Typeable
|
||||||
|
import qualified GHC.Stack as Stack
|
||||||
|
import qualified Witch.Cast as Cast
|
||||||
|
import qualified Witch.Identity as Identity
|
||||||
|
import qualified Witch.TryCast as TryCast
|
||||||
|
import qualified Witch.TryCastException as TryCastException
|
||||||
|
|
||||||
|
-- | This is the same as 'id' except that it requires a type application. This
|
||||||
|
-- can be an ergonomic way to pin down a polymorphic type in a function
|
||||||
|
-- pipeline. For example:
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > f . (\ x -> x :: Int) . g
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > f . as @Int . g
|
||||||
|
as :: forall s source . Identity.Identity s ~ source => source -> source
|
||||||
|
as = id
|
||||||
|
|
||||||
|
-- | This is the same as 'Cast.cast' except that it requires a type
|
||||||
|
-- application for the @source@ type.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > cast (x :: s)
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > from @s x
|
||||||
|
from
|
||||||
|
:: forall s target source
|
||||||
|
. (Identity.Identity s ~ source, Cast.Cast source target)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
from = Cast.cast
|
||||||
|
|
||||||
|
-- | This is the same as 'Cast.cast' except that it requires a type
|
||||||
|
-- application for the @target@ type.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > cast x :: t
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > into @t x
|
||||||
|
into
|
||||||
|
:: forall t source target
|
||||||
|
. (Identity.Identity t ~ target, Cast.Cast source target)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
into = Cast.cast
|
||||||
|
|
||||||
|
-- | This function converts from some @source@ type into some @target@ type,
|
||||||
|
-- applies the given function, then converts back into the @source@ type. This
|
||||||
|
-- is useful when you have two types that are isomorphic but some function
|
||||||
|
-- that only works with one of them.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > from @t . f . from @s
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > over @t f
|
||||||
|
over
|
||||||
|
:: forall t source target
|
||||||
|
. ( Identity.Identity t ~ target
|
||||||
|
, Cast.Cast source target
|
||||||
|
, Cast.Cast target source
|
||||||
|
)
|
||||||
|
=> (target -> target)
|
||||||
|
-> source
|
||||||
|
-> source
|
||||||
|
over f = Cast.cast . f . Cast.cast
|
||||||
|
|
||||||
|
-- | This function first converts from some @source@ type into some @through@
|
||||||
|
-- type, and then converts that into some @target@ type. Usually this is used
|
||||||
|
-- when writing 'Cast.Cast' instances. Sometimes this can be used to work
|
||||||
|
-- around the lack of an instance that should probably exist.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > from @u . into @u
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > via @u
|
||||||
|
via
|
||||||
|
:: forall u source target through
|
||||||
|
. ( Identity.Identity u ~ through
|
||||||
|
, Cast.Cast source through
|
||||||
|
, Cast.Cast through target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
via = Cast.cast . (\x -> x :: through) . Cast.cast
|
||||||
|
|
||||||
|
-- | This is the same as 'TryCast.tryCast' except that it requires a type
|
||||||
|
-- application for the @source@ type.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > tryCast (x :: s)
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > tryFrom @s x
|
||||||
|
tryFrom
|
||||||
|
:: forall s target source
|
||||||
|
. (Identity.Identity s ~ source, TryCast.TryCast source target)
|
||||||
|
=> source
|
||||||
|
-> Either (TryCastException.TryCastException source target) target
|
||||||
|
tryFrom = TryCast.tryCast
|
||||||
|
|
||||||
|
-- | This is the same as 'TryCast.tryCast' except that it requires a type
|
||||||
|
-- application for the @target@ type.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > tryCast x :: Either (TryCastException s t) t
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > tryInto @t x
|
||||||
|
tryInto
|
||||||
|
:: forall t source target
|
||||||
|
. (Identity.Identity t ~ target, TryCast.TryCast source target)
|
||||||
|
=> source
|
||||||
|
-> Either (TryCastException.TryCastException source target) target
|
||||||
|
tryInto = TryCast.tryCast
|
||||||
|
|
||||||
|
-- | This is similar to 'via' except that it works with 'TryCast.TryCast'
|
||||||
|
-- instances instead. This function is especially convenient because juggling
|
||||||
|
-- the types in the 'TryCastException.TryCastException' can be tedious.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > fmap (tryFrom @u) . tryInto @u
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > tryVia @u
|
||||||
|
tryVia
|
||||||
|
:: forall u source target through
|
||||||
|
. ( Identity.Identity u ~ through
|
||||||
|
, TryCast.TryCast source through
|
||||||
|
, TryCast.TryCast through target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> Either (TryCastException.TryCastException source target) target
|
||||||
|
tryVia s = case TryCast.tryCast s of
|
||||||
|
Left _ -> Left $ TryCastException.TryCastException s
|
||||||
|
Right u -> case TryCast.tryCast (u :: through) of
|
||||||
|
Left _ -> Left $ TryCastException.TryCastException s
|
||||||
|
Right t -> Right t
|
||||||
|
|
||||||
|
-- | This function is like 'TryCast.tryCast' except that it will throw an
|
||||||
|
-- impure exception if the conversion fails.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > either throw id . cast
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > unsafeCast
|
||||||
|
unsafeCast
|
||||||
|
:: forall source target
|
||||||
|
. ( Stack.HasCallStack
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
unsafeCast = either Exception.throw id . TryCast.tryCast
|
||||||
|
|
||||||
|
-- | This function is like 'from' except that it will throw an impure
|
||||||
|
-- exception if the conversion fails.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > either throw id . from @s
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > unsafeFrom @s
|
||||||
|
unsafeFrom
|
||||||
|
:: forall s target source
|
||||||
|
. ( Identity.Identity s ~ source
|
||||||
|
, Stack.HasCallStack
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
unsafeFrom = unsafeCast
|
||||||
|
|
||||||
|
-- | This function is like 'into' except that it will throw an impure
|
||||||
|
-- exception if the conversion fails.
|
||||||
|
--
|
||||||
|
-- > -- Avoid this:
|
||||||
|
-- > either throw id . into @t
|
||||||
|
-- >
|
||||||
|
-- > -- Prefer this:
|
||||||
|
-- > unsafeInto @t
|
||||||
|
unsafeInto
|
||||||
|
:: forall t source target
|
||||||
|
. ( Identity.Identity t ~ target
|
||||||
|
, Stack.HasCallStack
|
||||||
|
, TryCast.TryCast source target
|
||||||
|
, Show source
|
||||||
|
, Typeable.Typeable source
|
||||||
|
, Typeable.Typeable target
|
||||||
|
)
|
||||||
|
=> source
|
||||||
|
-> target
|
||||||
|
unsafeInto = unsafeCast
|
1708
src/test/Main.hs
1708
src/test/Main.hs
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
|||||||
resolver: lts-17.0
|
resolver: nightly-2021-04-06
|
||||||
|
63
witch.cabal
63
witch.cabal
@ -1,52 +1,77 @@
|
|||||||
cabal-version: >= 1.10
|
cabal-version: 2.2
|
||||||
|
|
||||||
|
name: witch
|
||||||
|
version: 0.0.0.5
|
||||||
|
synopsis: Convert values from one type into another.
|
||||||
|
description: Witch converts values from one type into another.
|
||||||
|
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
category: Data
|
category: Data
|
||||||
description: Witch converts values from one type into another.
|
|
||||||
extra-source-files: CHANGELOG.markdown README.markdown
|
extra-source-files: CHANGELOG.markdown README.markdown
|
||||||
license-file: LICENSE.txt
|
license-file: LICENSE.txt
|
||||||
license: ISC
|
license: ISC
|
||||||
maintainer: Taylor Fausak
|
maintainer: Taylor Fausak
|
||||||
name: witch
|
|
||||||
synopsis: Convert values from one type into another.
|
|
||||||
version: 0.0.0.5
|
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
location: https://github.com/tfausak/witch
|
location: https://github.com/tfausak/witch
|
||||||
type: git
|
type: git
|
||||||
|
|
||||||
library
|
common basics
|
||||||
build-depends:
|
build-depends:
|
||||||
base >= 4.13.0 && < 4.16
|
, base >= 4.13.0 && < 4.16
|
||||||
, bytestring >= 0.10.10 && < 0.11
|
, bytestring >= 0.10.12 && < 0.11
|
||||||
, containers >= 0.6.2 && < 0.7
|
, containers >= 0.6.2 && < 0.7
|
||||||
, text >= 1.2.4 && < 1.3
|
, text >= 1.2.4 && < 1.3
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
exposed-modules: Witch
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
-Weverything
|
-Weverything
|
||||||
-Wno-implicit-prelude
|
-Wno-implicit-prelude
|
||||||
|
-Wno-missing-deriving-strategies
|
||||||
|
-Wno-missing-export-lists
|
||||||
|
-Wno-missing-exported-signatures
|
||||||
-Wno-redundant-constraints
|
-Wno-redundant-constraints
|
||||||
-Wno-safe
|
-Wno-safe
|
||||||
-Wno-unsafe
|
-Wno-unsafe
|
||||||
hs-source-dirs: src/lib
|
|
||||||
|
|
||||||
if impl(ghc >= 8.10)
|
if impl(ghc >= 8.10)
|
||||||
ghc-options:
|
ghc-options:
|
||||||
-Wno-missing-safe-haskell-mode
|
-Wno-missing-safe-haskell-mode
|
||||||
-Wno-prepositive-qualified-module
|
-Wno-prepositive-qualified-module
|
||||||
|
|
||||||
test-suite test
|
library
|
||||||
|
import: basics
|
||||||
|
|
||||||
build-depends:
|
build-depends:
|
||||||
base
|
, template-haskell >= 2.15.0 && < 2.18
|
||||||
, bytestring
|
exposed-modules:
|
||||||
, containers
|
Witch
|
||||||
, hspec >= 2.7.6 && < 2.8
|
Witch.Cast
|
||||||
, QuickCheck >= 2.13.2 && < 2.15
|
Witch.Identity
|
||||||
, text
|
Witch.Instances
|
||||||
|
Witch.Lift
|
||||||
|
Witch.TryCast
|
||||||
|
Witch.TryCastException
|
||||||
|
Witch.Utility
|
||||||
|
hs-source-dirs: src/lib
|
||||||
|
|
||||||
|
if impl(ghc >= 9.0)
|
||||||
|
hs-source-dirs: src/ghc-9.0
|
||||||
|
else
|
||||||
|
if impl(ghc >= 8.10)
|
||||||
|
hs-source-dirs: src/ghc-8.10
|
||||||
|
else
|
||||||
|
hs-source-dirs: src/ghc-8.8
|
||||||
|
|
||||||
|
test-suite test
|
||||||
|
import: basics
|
||||||
|
|
||||||
|
build-depends:
|
||||||
|
, hspec >= 2.7.9 && < 2.8
|
||||||
, witch
|
, witch
|
||||||
default-language: Haskell2010
|
ghc-options:
|
||||||
ghc-options: -rtsopts -threaded
|
-rtsopts
|
||||||
|
-threaded
|
||||||
|
-Wno-all-missed-specialisations
|
||||||
hs-source-dirs: src/test
|
hs-source-dirs: src/test
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
|
Loading…
Reference in New Issue
Block a user