I'm confused about data kinds. Suppose we have
{-# LANGUAGE DataKinds #-}
...
data Format
= Photo
{ bytes :: Int
}
| Video
{ bytes :: Int
, durationSec :: Int
}
I want to make then function with promouted type:
createVideo :: Int -> Int -> 'Video
createVideo _ _ = error "Not implemented"
Compiller this will ask us for parameters, and with them it give msg 'Video Int Int has kind `Format'. i would like this compile-time behavior similar to kotlin:
sealed class Format {
abstract val bytes: Int
data class Photo(override val bytes: Int) : Format()
data class Video(override val bytes: Int, val durationSec: Int) : Format()
}
private fun createVideo(bytes: Int, durationSec: Int) : Format.Video {
return Format.Video(bytes, durationSec)
}
fun main() {
val video: Format = createVideo(bytes = 0, durationSec = 0) // correct
val video2: Format.Video = createVideo(bytes = 0, durationSec = 0) // correct
val video3: Format.Photo = createVideo(bytes = 0, durationSec = 0) // compiler error
}
In repl https://pl.kotl.in/2G9E1Cbgs
Data kinds do not provide a direct mechanism to check the value of a data structure at compile time. In other words, if you have a data type:
that is suitable for representing photos and videos, using the
DataKinds
extension to promote it to the type level does not allow you to write functions that accept only 10-second videos:or even generate a
Format
value that is guaranteed to be a video:From a technical standpoint, the new types
Photo
andVideo
that are promoted by theDataKinds
extension are types of a kind (specifically, the new kindFormat
) that does not support values. So, while values of typeInt
exist (becauseInt
is of kind*
, the kind of types that have values), no values of typePhoto 0
orVideo 128000 10
exist. So, you can't use these types as return types (or argument types) of functions.So, what use are data kinds? Well, if you have a data structure that you want to somehow constrain at compile time, you don't promote that data structure. Instead you promote other data structures to use as tools to write a type-level program to constrain the target data structure.
In your example, the constraints you want to put on your data structure are really quite modest: you want to check if the data structure is or isn't a
Video
. You don't need data kinds to do this. Regular Haskell types are adequate if you just rearrange your data structure a bit. Separating the formats into two types:is enough to distinguish videos from non-videos at compile time. If you have parts of your program where you want to work with a value that can be either a video or a photo, you can introduce a sum type:
Data kinds become useful when the constraints become more complicated. For example, suppose you wanted to censor photos videos to make them safe for the whole family:
and allow users to generate screenshots:
You might like, at compile time, to ensure you don't accidentally censor a video twice, or show an uncensored video to a younger audience, or let someone bypass your censoring by taking screenshots of uncensored videos and passing them off as censored photos.
You could accomplish this by introducing more types:
so you can write things like:
That's pretty messy, though. And suppose you want to add some constraints on video length to prevent spammers from submitting lots of short videos, or to avoid tying up your servers censoring really long videos. How many types along the line of
ShortUncensoredVideo
do you want to define?In a situation like this, you can use data kinds to develop a type-level "language" for describing attributes of your data structures:
Now we can write things like:
and catch problems at compile time:
Note that the promoted types (
Censored
,Uncensored
,Short
,Medium
,Long
) and kinds (Censoring
andLength
) are not directly related to the unpromoted typesPhoto
,Video
, andFormat
that they describe. As I say, this is how data kinds are typically used.Full code example: