问题
I have a situation where I am at the moment using the extremely scary function unsafeCoerce. It's not for anything important fortunately, but I was wondering whether this seems to be a safe usage of this function, or whether there would be another way to solve this particular problem that other people know of.
The code I have is something like the following:
data Token b = Token !Integer
identical :: Token a -> Token b -> Bool
identical (Token a) (Token b) = a == b
data F a = forall b. F (Token b) (a -> b)
retrieve :: Token b -> F a -> Maybe (a -> b)
retrieve t (F t' f) = if identical t t' then Just (unsafeCoerce f) else Nothing
Two additional things to note, are that these tokens are used within a monad which I use to ensure that the supply of integers for them is unique (i.e. I don't make the same token twice). I also use a forall quantified shadow type variable, in the same way as the ST monad, to make sure that (assuming only the methods I expose in the module are used) there is no way to return a token (or in fact even an F) from the monad without it being a type error. I also don't expose the token constructor.
I think, as far as I can see, this should be a safe usage of unsafeCoerce, as I can say with (I hope) pretty high confidence that the value I am coercing is in fact of exactly the type that I am coercing it to, but I may be wrong. I have also tried using Data.Typeable, which works nicely, but at the moment I am trying this to avoid the Typeable constraint, especially as gcast seems to do something in many ways similar, and I would still need the tokens anyway to distinguish between different Fs of the same type.
Thanks very much for any help/advice.
回答1:
You have implemented a restricted form of dynamic typing, broadly following the style of Data.Dynamic -- namely, paring an (opaque) value with evidence of its type. At runtime you can do an unsafe coercion, based on the evidence you shipped with the data.
fromDyn (Dynamic t v) def
| typeOf def == t = unsafeCoerce v
| otherwise = def
This is the canoncial approach, with a long history, going back to:
Mart´ın Abadi, Luca Cardelli, Benjamin Pierce, and Gordon Plotkin. Dynamic typing in a statically typed language. ACM Transactions on Programming Languages and Systems, 13(2):237–268, April 1991.
The safety of the approach relies on the unforgeability of runtime type tokens. In your case, anyone can build a token that equates two types -- you would need to guarantee a 1-1 mapping from types to tokens, and ensure that a malicious user can't construct incorrect tokens. In the GHC case, we trust the Typeable instances (and module abstraction).
回答2:
It's not safe per se:
oops :: F Bool
oops = F (Token 12) not
bad :: Token Int
bad = Token 12
*Token> maybe 3 ($ True) $ retrieve bad oops
1077477808
F a
is an existentially quantified type, you don't know what type b
went into it. Since identical
doesn't care about the type parameters to Token
, it cannot check whether the supplied b
from retrieve
's first argument has anything to do with what went into the F a
.
Whether your protection
Two additional things to note, are that these tokens are used within a monad which I use to ensure that the supply of integers for them is unique (i.e. I don't make the same token twice). I also use a forall quantified shadow type variable, in the same way as the ST monad, to make sure that (assuming only the methods I expose in the module are used) there is no way to return a token (or in fact even an F) from the monad without it being a type error. I also don't expose the token constructor.
is strong enough to make it safe in practice, I cannot tell without seeing it. If indeed no Token
s can be created outside the computation, and the Integer
value of the Token
uniquely characterises the type parameter, it would be safe.
来源:https://stackoverflow.com/questions/14322311/is-this-a-safe-use-of-unsafecoerce