Suppose I have a secret which I keep in a type
data Secret a = Secret a deriving Functor, Show
sec :: Secret String
and I want to allow computation on the secret, and some way to view results e.g.
getSecretHash :: Show a => Secret a -> String
or
askQuestion :: (a->Bool) -> Secret a -> Bool
but I don't want to allow the secret to be directly extracted (I know you could bruteforce one of the above ways, but assume the secret is large so this is unfeasible).
Of course someone could just write
reveal :: Secret a -> a
reveal (Secret x) = x
and I know I can prevent this by putting secret in a module and not exporting the constructor but instead giving a makeSecret :: a->Secret a
, but I want to know if there's a way to do it using the type system.
Without hiding the constructor, how can I make a type that can't have its value arbitrarily extracted?
This
askQuestion :: (a -> Bool) -> Secret a -> Bool
looks a bit like a flipped version of runCont
λ import Control.Monad.Trans.Cont
λ :t runCont
runCont :: forall r a. Cont r a -> (a -> r) -> r
λ :set -XTypeApplications
λ :t runCont @Bool
runCont @Bool :: forall a. Cont Bool a -> (a -> Bool) -> Bool
λ :t flip (runCont @Bool)
flip (runCont @Bool) :: forall a. (a -> Bool) -> Cont Bool a -> Bool
So perhaps in that respect your Secret
type is Cont Bool
, and you can create values with cont:
cont :: forall a. ((a -> Bool) -> Bool) -> Cont Bool a
makeSecret :: forall a. a -> Cont Bool a
makeSecret a = cont $ \f -> f a
The actual value is hidden behind a function.
Without hiding the constructor, how can I make a type that can't have its value arbitrarily extracted?
No. Hiding the constructor is precisely the right tool for that, and the only reasonable method I can think of.
Well, if using fancy Haskell types isn't a requirement, then you can use the old function closure trick. Just define the data type as the query function:
data Secret a = Secret { query :: (a -> Bool) -> Bool }
Exporting a helper function to construct secrets may be helpful (though it's entirely optional, as the constructor is public and anyone can make their own makeSecret
function):
makeSecret :: a -> Secret a
makeSecret x = Secret (\f -> f x) -- or Secret ($x) if you're feeling clever
The definition of askQuestion
is straightforward:
askQuestion :: (a -> Bool) -> Secret a -> Bool
askQuestion = flip query
I guess this is ultimately similar to danidiaz
's answer, but the monad machinery isn't really necessary just to store a secret in a function.
Note that, if you need a functor instance for this Secret
, Haskell has no problem deriving one, and it works as expected:
> askQuestion (=="Stack Overflow") $ fmap (++" Overflow") $ makeSecret "Stack"
True
I guess technically you can get it back out by cheating, like so, but I'm not sure that any of the other methods can avoid this:
> askQuestion (\x -> unsafePerformIO (putStrLn (show x)
>> return False)) $ makeSecret "secret"
"secret"
False
>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With