Considering first-class-families library, I have a code:
import Fcf.Data.Common
import Fcf.Core
type T a b c = '(a, '(b, c))
data D a b t = D
{ a :: a
, b :: b
, c :: Eval (Fst t)
, d :: Eval (Fst (Eval (Snd t)))
, e :: Eval (Snd (Eval (Snd t)))
}
type MyT = T Int Int Bool
data C = C (D Int Int MyT)
with nesting only because no Third
(similarly to Fst
and Snd
). Is it possible to avoid this nesting in some more intelligent way? Maybe there is some operator allowing more flat "structure"? If no, what can be used instead of it?
Or from another point of view, maybe this code is not good:
Eval (Fst (Eval (Snd t)))
and there is a way to avoid the second Eval
?
Finally, I want D
taking type-level tuple of size > 2 and to pass it in a "flat" form if it's possible.
In case this is an X-Y problem, please note that you don't need the first-class-families
package to implement simple type-level functions. You can just use type families directly. So, the following will work fine:
{-# LANGUAGE TypeFamilies #-}
type family Fst triple where
Fst (a,b,c) = a
type family Snd triple where
Snd (a,b,c) = b
type family Thd triple where
Thd (a,b,c) = c
type T a b c = (a, b, c)
data D a b t = D
{ a :: a
, b :: b
, c :: Fst t
, d :: Snd t
, e :: Thd t
}
type MyT = T Int Int Bool
data C = C (D Int Int MyT)
Alternatively, you can define:
type T a b c = '(a, b, c)
and modify the type family definitions with ticks:
type family Fst triple where
Fst '(a,b,c) = a
-- etc. --
which will work too.
Two observations:
First, there's actually a big conceptual difference between the first "no ticks" version and the second "ticks" version. It's the type-level equivalent of working in a dynamically typed language ("no ticks") and a statically typed language ("ticks"), but if you don't have a compelling reason to use the "ticks" version, you should stick with the "no ticks" version, as it's more straightforward and perfectly idiomatic and -- like dynamically typed programming in general -- comes with some usability advantages, like allowing you to define:
type family Fst tuple where
Fst (a,b) = a
Fst (a,b,c) = a
Fst (a,b,c,d) = a
-- and so on --
and use the same Fst
function on different length tuples.
Second, the purpose of first-class-families
is to permit partially applied type-level functions. If you needed to pass Fst
to a Map
function, for example:
type Result = Map Fst '[(Double,Char,String),(Int,Double,Double)]
then the type family definitions in this answer wouldn't work because type families can only appear in type expressions fully applied to all their arguments.
If you don't need to use partially (or unapplied) type-level functions, then you don't need first-class-families
.