patternModerate
Capture the notion of invertible functions
Viewed 0 times
notionthecaptureinvertiblefunctions
Problem
I find sometimes it is useful to capture the notion of invertible functions.
The idea is that if two functions
Moreover, if there
The following is my attempt to implement this in Haskell, and I'm wondering if an existing library can do the same thing (or even more general thing) for me.
Also advice and comments about my code are appreciated.
Implementation
First I use records to store two functions:
And then few helper functions:
In the above code
available to values of type
In addition,
```
rempty :: Invertible a a
rempty = selfInv id
rappend :: Invertible a b
-> Invertible b c
-> Invertible a c
(Invertible f
The idea is that if two functions
f :: a -> b and g :: b -> a are the inverse function of each other, and if there is another function h :: b -> b, then h can also work on values of type a.Moreover, if there
f' and g' are another pair of functions that are the inverse function of each other, (f,g) and (f',g') can actually be composed to (f' . f, g . g') and the invertibility still holds.The following is my attempt to implement this in Haskell, and I'm wondering if an existing library can do the same thing (or even more general thing) for me.
Also advice and comments about my code are appreciated.
Implementation
First I use records to store two functions:
data Invertible a b = Invertible
{ into :: a -> b
, back :: b -> a
}into means "convert a into b" while back means "convert b back to a".And then few helper functions:
selfInv :: (a -> a) -> Invertible a a
selfInv f = Invertible f f
flipInv :: Invertible a b -> Invertible b a
flipInv (Invertible f g) = Invertible g f
borrow :: Invertible a b -> (b -> b) -> a -> a
borrow (Invertible fIn fOut) g = fOut . g . fIn
liftInv :: (Functor f) => Invertible a b -> Invertible (f a) (f b)
liftInv (Invertible a b) = Invertible (fmap a) (fmap b)In the above code
borrow will use the pair of functions to make its last argument gavailable to values of type
a. And changing borrow f to borrow (flipInv f) will make g available to values of type b. Therefore borrow captures my initial idea of making a function of type b -> b available for values of a if a and b can be converted to each other.In addition,
Invertible forms a monoid-like structure, I use rappend and rempty to suggest a similiarity between it and Monoid:```
rempty :: Invertible a a
rempty = selfInv id
rappend :: Invertible a b
-> Invertible b c
-> Invertible a c
(Invertible f
Solution
Naming:
There are a lot of names I'd change. Some of them matter more than others (local names in a let are not that big a deal even if they're terrible, but a name for a widely used function should be suggestive, if possible). Some of them I'm more sure of than others.
I'd change
I'd change the local
I'd change
The
I'd change
I can see why you named
I'd also change
All Namechanges in One Spot:
DD, DL, DR, DU -> DOWN, LEFT, RIGHT, UP
Invertible -> Transform
invertible -> transform
mirrorI -> mirror
diagonalI -> diagonal
selfInv -> involution
liftInv -> lift
flipInv -> backwards
into, back -> fwd, rev
fIn, fOut -> f, f'
rempty -> idT
rappend -> >>>
Instances that don't work:
Monad, Applicative, and Functor don't work because
Monoid doesn't work because
Arrow seems like it should work at first, but
Category fits perfectly, but when I tried to get it to work, the compiler complained about all the uses of
Instances that do work:
None?
Non-Naming Changes:
The suggestion in the comments by mjolka is correct, and the resulting code:
looks nicer and is clearer.
This:
is a bit ugly, and can be simplified to this:
I'd introduce a new function that abstracts the pattern in the parentheses in
making my suggested namechanges, it becomes:
```
caesar
There are a lot of names I'd change. Some of them matter more than others (local names in a let are not that big a deal even if they're terrible, but a name for a widely used function should be suggestive, if possible). Some of them I'm more sure of than others.
I'd change
borrow to within. The old name isn't bad, but I think the new one is suggestive in the right way. I'd change Invertible to Transform, since an invertible function is just the one function, and we're talking about a pair of related functions. Bijection is the mathematical name for it, and it would make a good name too, but it seems (to me at least) to suggest that the domain and range are the entire input and output types, and not every function we'd want to use with this fits that.I'd change the local
invertible to transform to match, so borrow invertible (map compactLeft) becomes within transform (map compactLeft).I'd change
mirrorI -> mirror and diagonalI -> diagonal; the I doesn't add anything, it just looks as if you're using roman numerals at first. I'd also change DD, DL, DR, DU -> DOWN, LEFT, RIGHT, UP, as it's just clearer.The
Inv suffix doesn't help. Not all of the functions have it, and it isn't spelled out enough to be suggestive. Also, if we change it from Invertible, it no longer makes sense. So, selfInv -> involution, liftInv -> lift, flipInv -> backwards. An involution is the mathematical name for a function that is its own inverse (and if you didn't know that, don't feel bad, I was only guessing there might be a name for it until I googled it). flip is already taken, and I think backwards is more suggestive (reverse would have been nice too, but it's also taken). lift isn't taken as far as I know, but it's the sort of thing that seems like it might be. If so, liftT or liftTr might work (or liftB if we're using Bijection).I'd change
fIn, fOut -> f, f'. Using f' doesn't quite say "I'm an inverse", but it suggests it somewhat if we know how Transform is defined/meant to be used.I can see why you named
rempty and rappend as you did, but the names made me (and would probably make other people) want to try to make it a Monoid instance, and that doesn't work. I'd change rempty -> idT, rappend -> >>>. >>> matches the Category class, and is nicely intuitive (and doesn't need backquotes). I'm not really sure about idT, and it could probably be improved, but it still seems a bit nicer than rempty to me.I'd also change
into, back -> fwd, rev, but I don't have much justification here. It seems a little more suggestive of a thing that can go both ways, instead of a box that you can go into and come out of, but can't be flipped inside out. The original names here are good.All Namechanges in One Spot:
DD, DL, DR, DU -> DOWN, LEFT, RIGHT, UP
Invertible -> Transform
invertible -> transform
mirrorI -> mirror
diagonalI -> diagonal
selfInv -> involution
liftInv -> lift
flipInv -> backwards
into, back -> fwd, rev
fIn, fOut -> f, f'
rempty -> idT
rappend -> >>>
Instances that don't work:
Monad, Applicative, and Functor don't work because
fmap :: (a -> b) -> f a -> f b and that doesn't fit.Monoid doesn't work because
mappend :: a -> a -> a, and our composition takes 2 different types and returns yet another different type.Arrow seems like it should work at first, but
arr :: (b -> c) -> a b c. The types match nicely, but we'd need a way that makes sense to take an arbitrary function and find its inverse. Functions and Arrows go one way, but we want to go both ways.Category fits perfectly, but when I tried to get it to work, the compiler complained about all the uses of
. in the program being ambiguous. There may be a way to make it work, but looking at Control.Category, the gains were almost nonexistent, so I abandoned the attempt. The interface (>>> and <<< for forward and backward composition), was nice, though, so I kept it.Instances that do work:
None?
Non-Naming Changes:
The suggestion in the comments by mjolka is correct, and the resulting code:
modShift base offset = Transform f g
where
f x = (x + offset) `mod` base
g y = (y - offset) `mod` baselooks nicer and is clearer.
This:
compactLeft xs = nonZeros ++ replicate (((-) `on` length) xs nonZeros) 0
where nonZeros = filter (/= 0) xsis a bit ugly, and can be simplified to this:
compactLeft xs = take (length xs) $ nonZeros ++ repeat 0
where nonZeros = filter (/= 0) xsI'd introduce a new function that abstracts the pattern in the parentheses in
caesarCipher = liftInv (upperAsOrd
-- Char Int
`rappend` caesarShift
-- Int Int
`rappend` flipInv upperAsOrd)
-- Int Charmaking my suggested namechanges, it becomes:
```
caesar
Code Snippets
modShift base offset = Transform f g
where
f x = (x + offset) `mod` base
g y = (y - offset) `mod` basecompactLeft xs = nonZeros ++ replicate (((-) `on` length) xs nonZeros) 0
where nonZeros = filter (/= 0) xscompactLeft xs = take (length xs) $ nonZeros ++ repeat 0
where nonZeros = filter (/= 0) xscaesarCipher = liftInv (upperAsOrd
-- Char <-> Int
`rappend` caesarShift
-- Int <-> Int
`rappend` flipInv upperAsOrd)
-- Int <-> CharcaesarCipher = lift (upperAsOrd >>> caesarShift >>> backwards upperAsOrd)Context
StackExchange Code Review Q#44550, answer score: 10
Revisions (0)
No revisions yet.