Expanding recursive case of function in equational reasoning - liquid-haskell

Context: I am proving some properties about a Haskell quicksort implementation. The following code is all that is required to define a nondeterministic permute function. Note that I am using LList rather than Haskell's base [] type. The problem area is the permute_LCons_expansion_correct theorem (it's not much of a theorem).
-- Data. A list is either `LNil` or constructed with a `vhead` element and a
-- `vtail` list.
data LList a = LNil | LCons {vhead :: a, vtail :: LList a}
-- Function. Append two lists.
{-# reflect lappend #-}
lappend :: LList a -> LList a -> LList a
lappend LNil ys = ys
lappend (LCons x xs) ys = LCons x (lappend xs ys)
-- Function. Construct a list with 1 element.
{-# reflect llist1 #-}
llist1 :: a -> LList a
llist1 x = LCons x LNil
-- Function. Construct a list with 2 elements.
{-# reflect llist2 #-}
llist2 :: a -> a -> LList a
llist2 x y = llist1 x `lappend` llist1 y
-- Function. Map a list-function over a list and concatenate the resulting
-- lists (i.e. list-monadic bind).
{-# reflect lbind #-}
lbind :: LList a -> (a -> LList b) -> LList b
lbind LNil f = LNil
lbind (LCons x xs) f = f x `lappend` lbind xs f
-- Function. Map a function over a list.
{-# reflect lmap #-}
lmap :: (a -> b) -> (LList a -> LList b)
lmap f LNil = LNil
lmap f (LCons x xs) = LCons (f x) (lmap f xs)
{-# reflect lmap2 #-}
-- Function. Map a binary function over two lists (zipping).
lmap2 :: (a -> b -> c) -> LList a -> LList b -> LList c
lmap2 f xs ys = lbind xs (\x -> lbind ys (\y -> llist1 (f x y)))
-- Function. Nondeterministically split a list into two sublists.
{-# reflect split #-}
split :: LList a -> LList (LList a, LList a)
split LNil = llist1 (LNil, LNil)
split (LCons x xs) =
split xs
`lbind` \(ys, zs) ->
llist1 (LCons x ys, zs) `lappend` llist1 (ys, LCons x zs)
-- Function. Nondeterministically permute a list.
{-# reflect permute #-}
permute :: LList a -> LList (LList a)
permute LNil = llist1 LNil
permute (LCons x xs) =
split xs `lbind` \(ys, zs) ->
lmap2
(\ys' zs' -> ys' `lappend` llist1 x `lappend` zs')
(permute ys)
(permute zs)
-- Function. The expanded form of `permute` on an `LCons`.
{-# reflect permute_LCons_expansion #-}
permute_LCons_expansion :: a -> LList a -> LList (LList a)
permute_LCons_expansion x xs =
split xs
`lbind` ( \(ys, zs) ->
lmap2
(\ys' zs' -> ys' `lappend` llist1 x `lappend` zs')
(permute ys)
(permute zs)
)
-- Theorem. `permute_LCons_expansion` corresponds to `permute` on an `LCons`.
{-#
permute_LCons_expansion_correct :: x:a -> xs:LList a ->
{permute (LCons x xs) = permute_LCons_expansion x xs}
#-}
permute_LCons_expansion_correct :: a -> LList a -> Proof
permute_LCons_expansion_correct x xs =
permute (LCons x xs)
==. split xs
`lbind` ( \(ys, zs) ->
lmap2
(\ys' zs' -> ys' `lappend` llist1 x `lappend` zs')
(permute ys)
(permute zs)
)
==. permute_LCons_expansion x xs
*** QED
In permute_LCons_expansion_correct, the equations should be correct because the middle expression is just the body of the LCons case of permute which is also the definition of permute_LCons_expansion. However, when I compile, I get this LH error:
❯ cabal build
Build profile: -w ghc-8.10.3 -O1
{{I have removed project files info}}
**** LIQUID: UNSAFE ************************************************************
src/Test.hs:83:9: error:
Liquid Type Mismatch
.
The inferred type
VV : (Test.LList (Test.LList a))
.
is not a subtype of the required type
VV : {VV : (Test.LList (Test.LList a)) | permute (LCons x xs) == VV}
.
in the context
xs : (Test.LList a)
x : a
|
83 | ==. split xs
| ^^^^^^^^...
Why doesn't LH recognize the implicit equality? Note that I am using (==.) from Language.Haskell.Equational rather than Language.Haskell.ProofCombinators.
Configuration:
ghc version 8.10.3
LiquidHaskell Version 0.8.6.0
cabal version 3.2.0.0

Related

Transform list in type fixed length in haskell

With the answer to this question I was able to define a list in witch the length is part of the type:
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
data Nat = Z | S Nat
data ListF (n :: Nat) a where
Nil :: (ListF 'Z a)
Cons :: a -> ListF m a -> ListF ('S m) a
I would like be able to convert a normal list in to these kind of list. If a make these:
toListF :: [a] -> ListF n a
toListF [] = Nil
toListF (x:xs) = Cons x (toListF xs)
It does't type-check, because for [a] -> ListF n a to type-check, the function should return any Nat that the caller needs:
ListF.hs:11:14: error:
• Couldn't match type ‘n’ with ‘'Z’
‘n’ is a rigid type variable bound by
the type signature for:
toListF :: forall a (n :: Nat). [a] -> ListF n a
at ListF.hs:10:1-27
Expected type: ListF n a
Actual type: ListF 'Z a
• In the expression: Nil
In an equation for ‘toListF’: toListF [] = Nil
• Relevant bindings include
toListF :: [a] -> ListF n a (bound at ListF.hs:11:1)
|
11 | toListF [] = Nil
| ^^^
Failed, no modules loaded.
The logical type for toListF I think it wold be something like exists n. [a] -> ListF n a or [a] -> (exists n. ListF n a), but of course those are not valid haskell types.
It is possible to do what I am trying to do in haskell? And how?
There are existential types in Haskell.
{-# LANGUAGE DataKinds, GADTs, KindSignatures, RankNTypes #-}
data Nat = Z | S Nat
data ListF (n :: Nat) a where
Nil :: (ListF 'Z a)
Cons :: a -> ListF m a -> ListF ('S m) a
data SomeListF a = forall n . AList (ListF n a)
You cam convert from a regular list to SomeListF:
fromList :: [a] -> SomeListF a
fromList [] = AList Nil
fromList (x:xs) = someCons x (fromList xs) where
someCons x (AList zs) = AList (Cons x zs)
You can also recover ListF from SomeListF, but only in a restricted scope. The n in forall n cannot escape, so you cannot have something like
toListF :: SomeListF a -> ListF n a
but you can have this:
withSomeList :: (forall n . ListF n a -> b) -> SomeListF a -> b
withSomeList f (AList zs) = f zs
Inside the f argument, n is known and you can for example map your list and the length of the result is statically known to be the same as the length of the argument. Here's a silly example:
zipF :: ListF n a -> ListF n b -> ListF n (a, b)
zipF Nil Nil = Nil
zipF (Cons y ys) (Cons z zs) = Cons (y, z) (zipF ys zs)
mapF :: (a->b) -> ListF n a -> ListF n b
mapF _ Nil = Nil
mapF f (Cons z zs) = Cons (f z) (mapF f zs)
zipMapF :: (a->b) -> ListF n a -> ListF n (a, b)
zipMapF f zs = zipF zs (mapF f zs)
zipMapAny :: (a->b) -> ListF n a -> SomeListF (a, b)
zipMapAny f zs = AList (zipMapF f zs)
nums = fromList [1,2,3,4,5]
numsAndSquares = withSomeList (zipMapAny (\x -> x * x)) nums
zipMapAny "knows" that the length of all the lists inside it is the same, but it cannot leak that length to the result. You cannot have for example withSomeList (zipMapF (\x -> x * x)) nums because n would escape.

Creating a sum constructor value using `generics-sop`

In generics-sop, what is the idiomatic way to generically create a sum constructor value given both its position (index) and the product value of its args?
For example consider,
-- This can be any arbitrary type.
data Prop = Name String | Age Int | City String
deriving stock (GHC.Generic)
deriving anyclass (Generic)
-- Manually creating a sum constructor value (3rd constructor)
mkCityPropManual :: SOP I (Code Prop)
mkCityPropManual = from $ City "Chennai"
mkCityPropGeneric :: SOP I (Code Prop)
mkCityPropGeneric = SOP $ mkSumGeneric $ I ("Chennai" :: String) :* Nil
-- Generically creating it, independent of type in question
mkSumGeneric :: _
mkSumGeneric = undefined
How do you define mkSumGeneric?
Per https://github.com/kosmikus/SSGEP/blob/master/LectureNotes.pdf I figured the injection types might be useful here, but that's apparently only useful for either constructing all sum constructors, or building a homogenous sum list (to be collapsed).
A naive approach is to define a type-class like below, but I have a feeling there is a better way.
-- `XS` is known to be in `Code (a s)` per the class constraint this function is in
-- For complete code, see: https://github.com/Plutonomicon/plutarch/commit/a6343c99ba11390cc9dfa9c73c600a9d04cdf08c#diff-84126a8c05d2752f0764676cdcd6b10d826c154a6d4797b4334937e8a8e831f2R212-R230
mkSOP :: NP I '[xs] -> SOP I (Code (a s))
mkSOP = SOP . mkSum' #sx #'[xs] #rest
class MkSum (before :: [[Type]]) (x :: [Type]) (xs :: [[Type]]) where
mkSum' :: NP I x -> NS (NP I) (Append (Reverse before) (x ': xs))
instance MkSum '[] x xs where
mkSum' = Z
instance MkSum '[p1] x xs where
mkSum' = S . Z
instance MkSum '[p1, p2] x xs where
mkSum' = S . S . Z
instance MkSum '[p1, p2, p3] x xs where
mkSum' = S . S . S . Z
instance MkSum '[p1, p2, p3, p4] x xs where
mkSum' = S . S . S . S . Z
instance MkSum '[p1, p2, p3, p4, p5] x xs where
mkSum' = S . S . S . S . S . Z
instance MkSum '[p1, p2, p3, p4, p5, p6] x xs where
mkSum' = S . S . S . S . S . S . Z
EDIT: I've made MkSum general (see below), but something tells me that there is a more idiomatic way to do this all using generics-sop combinators. What would that be?
class MkSum (idx :: Fin n) xss where
mkSum :: NP I (TypeAt idx xss) -> NS (NP I) xss
instance MkSum 'FZ (xs ': xss) where
mkSum = Z
instance MkSum idx xss => MkSum ( 'FS idx) (xs ': xss) where
mkSum v = S $ mkSum #_ #idx #xss v
type family Length xs :: N.Nat where
Length '[] = 'N.Z
Length (x ': xs) = 'N.S (Length xs)
class Tail' (idx :: Fin n) (xss :: [[k]]) where
type Tail idx xss :: [[k]]
instance Tail' 'FZ xss where
type Tail 'FZ xss = xss
instance Tail' idx xs => Tail' ( 'FS idx) (x ': xs) where
type Tail ( 'FS idx) (x ': xs) = Tail idx xs
instance Tail' idx xs => Tail' ( 'FS idx) '[] where
type Tail ( 'FS idx) '[] = TypeError ( 'Text "Tail: index out of bounds")
class TypeAt' (idx :: Fin n) (xs :: [[k]]) where
type TypeAt idx xs :: [k]
instance TypeAt' 'FZ (x ': xs) where
type TypeAt 'FZ (x ': xs) = x
instance TypeAt' ( 'FS idx) (x ': xs) where
type TypeAt ( 'FS idx) (x ': xs) = TypeAt idx XS
EDIT: Adapting Eitan's answer below (which doesn't work for non-product types), I've simplified MkSum further as:
{- |
Infrastructure to create a single sum constructor given its type index and value.
- `mkSum #0 #(Code a) x` creates the first sum constructor;
- `mkSum #1 #(Code a) x` creates the second sum constructor;
- etc.
It is type-checked that the `x` here matches the type of nth constructor of `a`.
-}
class MkSum (idx :: Nat) (xss :: [[Type]]) where
mkSum :: NP I (IndexList idx xss) -> NS (NP I) xss
instance {-# OVERLAPPING #-} MkSum 0 (xs ': xss) where
mkSum = Z
instance
{-# OVERLAPPABLE #-}
( MkSum (idx - 1) xss
, IndexList idx (xs ': xss) ~ IndexList (idx - 1) xss
) =>
MkSum idx (xs ': xss)
where
mkSum x = S $ mkSum #(idx - 1) #xss x
-- | Indexing type-level lists
type family IndexList (n :: Nat) (l :: [k]) :: k where
IndexList 0 (x ': _) = x
IndexList n (x : xs) = IndexList (n - 1) xs
what is the idiomatic way to generically create a sum constructor
value given both its position (index) and the product value of its
args?
I might try something like this:
>>> :set -XKindSignatures -XDataKinds -XTypeOperators -XTypeApplications -XScopedTypeVariables -XAllowAmbiguousTypes
>>> :set -XFlexibleContexts -XFlexibleInstances -XMultiParamTypeClasses -XFunctionalDependencies -XUndecidableInstances
>>> import Data.Kind
>>> import Generics.SOP
>>> import GHC.TypeLits
>>> :{
class Summand (n :: Nat) as a | n as -> a where
summand :: a -> NS I as
instance {-# OVERLAPPING #-}
Summand 0 (a ': as) a where
summand = Z . I
instance {-# OVERLAPPABLE #-}
Summand (n-1) as a => Summand n (b ': as) a where
summand = S . summand #(n-1)
:}
>>> :{
[ summand #0 "0"
, summand #1 1
, summand #2 2
] :: [NS I '[String, Double, Int]]
:}
[Z (I "0"),S (Z (I 1.0)),S (S (Z (I 2)))]
EDIT More generally, abstract out the identity I for any interpretation f :: k -> Type
:set -XPolyKinds -XDataKinds -XTypeOperators -XTypeApplications -XScopedTypeVariables -XAllowAmbiguousTypes -XGADTs
:set -XFlexibleContexts -XFlexibleInstances -XMultiParamTypeClasses -XFunctionalDependencies -XUndecidableInstances
:set -XDerivingStrategies -XDeriveGeneric -XDeriveAnyClass
>>> :{
class Summand (n :: Nat) as a | n as -> a where
summand :: f a -> NS f as
instance {-# OVERLAPPING #-}
Summand 0 (a ': as) a where
summand = Z
instance {-# OVERLAPPABLE #-}
Summand (n-1) as a => Summand n (b ': as) a where
summand = S . summand #(n-1)
summandI :: forall n a as. Summand n as a => a -> NS I as
summandI = summand #n . I
summandGeneric
:: forall n b a as
. (IsProductType a as, Generic b, Summand n (Code b) as)
=> a -> b
summandGeneric = to . SOP . summand #n . productTypeFrom
:}
>>> data Foo = Bar {bar1 :: Char, bar2 :: Int} | Baz deriving stock (Show, GHC.Generic) deriving anyclass (Generic)
>>> summandGeneric #0 ('1',2) :: Foo
Bar {bar1 = '1', bar2 = 2}
>>> summandGeneric #1 () :: Foo
Baz
>>> :{
data Prop = Name String | Age Int | City String
deriving stock (Show, GHC.Generic)
deriving anyclass (Generic)
:}
>>> summandGeneric #1 #Prop (I 30)
Age 30
>>> summandGeneric #0 #Prop (I "John")
Name "John"

Splitting a type-level list in GHC

I am having a difficult time convincing GHC that certain properties
of list manipulation are true. Before I provide the code that I'm
working on, I'll give a brief example of the property I'm interested in.
Suppose we have some type-level list xs:
xs ~ '[ 'A, 'B, 'C, 'D, 'E, 'F ]
And we drop some of the elements and also take the same number of
elements:
Drop 2 xs ~ '[ 'C, 'D, 'E, 'F ]
TakeReverse 2 xs ~ '[ 'B, 'A ]
It should be obvious that if I apply Drop and TakeReverse
using the successor of 2, then I can just pop the 'C off of
Drop 2 xs and put it on top of TakeReverse 2 xs:
Drop 3 xs ~ '[ 'D, 'E, 'F ]
TakeReverse 3 xs ~ '[ 'C, 'B, 'A ]
The following code has a function named moveRight that attempts to
use this property. I have cut my actual code down to a somewhat small example that
illustrates the issue and has no dependencies.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE UndecidableInstances #-}
module Minimal where
import Data.Type.Equality
data Nat = Z | S Nat
data Natty (n :: Nat) where
Zy :: Natty 'Z
Sy :: Natty n -> Natty ('S n)
data HRec (vs :: [*]) where
HRecNil :: HRec '[]
HRecCons :: x -> HRec xs -> HRec (x ': xs)
data HProxy (vs :: [k]) where
HProxyNil :: HProxy '[]
HProxyCons :: HProxy xs -> HProxy (x ': xs)
data Parts n rs = Parts
{ partLeft :: HRec (Drop n rs)
, partRight :: HRec (TakeReverse n rs)
, partNatty :: Natty n
, partProxy :: HProxy rs
}
-- The type families Drop, Take, and TakeReverse
-- are all partial.
type family Drop (n :: Nat) (xs :: [k]) :: [k] where
Drop 'Z xs = xs
Drop ('S n) (x ': xs) = Drop n xs
type family Take (n :: Nat) (xs :: [k]) :: [k] where
Take 'Z xs = '[]
Take ('S n) (x ': xs) = x ': Take n xs
type family TakeReverse (n :: Nat) (xs :: [k]) :: [k] where
TakeReverse n xs = TakeReverseHelper '[] n xs
type family TakeReverseHelper (ys :: [k]) (n :: Nat) (xs :: [k]) :: [k] where
TakeReverseHelper res 'Z xs = res
TakeReverseHelper res ('S n) (x ': xs) = TakeReverseHelper (x ': res) n xs
moveRight :: Parts n rs -> Parts (S n) rs
moveRight (Parts pleft#(HRecCons pleftHead _) pright natty proxy) =
case dropOneProof natty proxy of
Refl -> Parts (dropOne pleft) (HRecCons pleftHead pright) (Sy natty) proxy
dropOneProof :: Natty n -> HProxy rs -> (Drop ('S n) rs :~: Drop ('S 'Z) (Drop n rs))
dropOneProof Zy _ = Refl
dropOneProof (Sy n) (HProxyCons rs) = case dropOneProof n rs of
Refl -> Refl
dropOne :: HRec rs -> HRec (Drop ('S 'Z) rs)
dropOne (HRecCons _ rs) = rs
This code does not compile because of moveRight. Basically, I'm able to
prove that dropping an additional element from the left side gives it
the right type, but I can't show that adding this element to the right
side makes it have the right type.
I'm really open to any changes. I'm fine with changing the type families,
introducing extra witnesses, etc., as long as moveRight becomes
possible to write.
If I need to further clarify what I am trying to do, please let me know. Thanks.
The problem with your representation is that you try to make the position of the split explicit, but don't enforce the validity of the position index.
As it is currently moveRight :: Parts n rs -> Parts (S n) rs can't be implemented, because if n is out of bounds, Take and the other type family applications cannot reduce and therefore no value can be given in the result.
There are many ways to solve this issue. The simplest is to make the types in the left and right parts of the context explicit:
type HZipper xs ys = (HRec xs, HRec ys)
moveRight :: HZipper xs (y ': ys) -> HZipper (y ': xs) ys
moveRight'(xs, HCons y ys) = (HCons y xs, ys)
This is actually just as strong representatation as your original Parts. provided that we enforce the bounds on the n index there. That's because both types indicate the whole list and the exact position of the split. From HZipper xs ys, the original type list can be computed as Reverse xs ++ ys with the appropriate ++ and Reverse type families. This is sometimes less convenient, but on the up side HZipper has much simpler internals.
Alternatively, you can hide the position of split existentially. In any case, this requires proof writing for moveRight:
import Data.Type.Equality
import Data.Proxy
data HRec vs where
HNil :: HRec '[]
HCons :: x -> HRec xs -> HRec (x ': xs)
type family (++) xs ys where
'[] ++ ys = ys
(x ': xs) ++ ys = x ': (xs ++ ys)
type family Reverse xs where
Reverse '[] = '[]
Reverse (x ': xs) = Reverse xs ++ '[x]
data HZipper xs where
HZipper :: HRec ys -> HRec zs -> HZipper (Reverse ys ++ zs)
hcat :: HRec xs -> HRec ys -> HRec (xs ++ ys)
hcat HNil ys = ys
hcat (HCons x xs) ys = HCons x (hcat xs ys)
hreverse :: HRec xs -> HRec (Reverse xs)
hreverse HNil = HNil
hreverse (HCons x xs) = hreverse xs `hcat` (HCons x HNil)
catAssoc :: HRec xs -> Proxy ys -> Proxy zs -> (xs ++ (ys ++ zs)) :~: ((xs ++ ys) ++ zs)
catAssoc HNil xs ys = Refl
catAssoc (HCons x xs) ys zs = case catAssoc xs ys zs of
Refl -> Refl
moveRight :: HZipper xs -> HZipper xs
moveRight (HZipper ls HNil) = HZipper ls HNil
moveRight (HZipper ls (HCons (x :: x) (xs :: HRec xs))) =
case catAssoc (hreverse ls) (Proxy :: Proxy '[x]) (Proxy :: Proxy xs) of
Refl -> HZipper (HCons x ls) xs
There's a third possibility, namely adding an existential bounds check inside the original Parts, or having moveRight :: InBounds (S n) rs -> Parts n rs -> Parts (S n) rs, where InBounds is a proof of inbounds-ness. Or we could have InBounds (S n) rs => ... with InBounds a type family returning a class constraint. This approach would also require quite a bit of proof-writing though.

Map with multiple arguments an Trees

i have a problem with a map function and recursion.
I have a Tree data structure like this:
data Tree a = T a [Tree a] deriving (Eq,Ord,Show)
I already have a working function to count "through" the Tree which works.
count :: Tree a -> Int
count (T _ xs) = 1 + sum(map count xs)
No i want a function to proof every element of the tree with a predicate
filterKnoten :: (a -> Bool) -> Tree a -> [a]
filterKnoten p (T x []) = (if p(x) then [x] else [])
filterKnoten p (T x xs)
| p(x) == True = x:(map multP xs)
| p(x) == False = map multP xs
where multP = filterKnoten p
Some sample date would be
ex1 = T True [T False [T True[]] , T True []]
No when i call the method with for example
filterKnoten (==True) ex1
As a result I want to have list with all elements which fits my predicate, but the compile gives me this when i want to load the module
Couldn't match type `a' with `[a]'
`a' is a rigid type variable bound by
the type signature for filterKnoten :: (a -> Bool) -> Tree a -> [a]
at WS11.hs:138:17
Expected type: Tree a -> a
Actual type: Tree a -> [a]
In the first argument of `map', namely `multP'
In the expression: map multP xs
In an equation for `filterKnoten':
filterKnoten p (T x xs)
| p (x) == True = x : (map multP xs)
| p (x) == False = map multP xs
where
multP = filterKnoten p
Failed, modules loaded: none.
So my question, why does map works with count and not with filterKnoten?
Thanks in advance
I imagine that writing the function using recursion is a useful exercise, but from a practical standpoint, you can just derive all of these functions with GHC extensions:
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
import Data.Foldable
import Data.Traversable
data Tree a = T a [Tree a] deriving (Eq,Ord,Show, Functor, Traversable, Foldable)
filterTree :: (a -> Bool) -> Tree a -> [a]
filterTree f = filter f . toList -- toList is defined in Foldable
You don't need Traversable for this example, it is just one of the other useful things you can derive.
Your major problem is that you're mapping a function of type T a -> [a] which gives you back [[a]] instead of the [a] you want. This can be fixed by changing map to concatMap from Data.List
import Data.List
...
filterKnoten :: (a -> Bool) -> Tree a -> [a]
filterKnoten p (T x []) = if p x then [x] else []
filterKnoten p (T x xs)
| p x = x:(concatMap multP xs)
| otherwise = concatMap multP xs
where multP = filterKnoten p
Notice that I've also gotten rid of the useless ==True and ==False's. x == True is precisely the same as x and when we're talking about to cases there's no point in computing the same thing twice. Especially since it's potentially very expensive. otherwise is just a synonym for True that prelude provides. I've also gotten rid of some of the unnecessary parens.
Finally, you can just dump the entire first case since map and concatMap work on empty lists.

How to partition a list in Haskell?

I want to take a list (or a string) and split it into sub-lists of N elements. How do I do it in Haskell?
Example:
mysteryFunction 2 "abcdefgh"
["ab", "cd", "ef", "gh"]
cabal update
cabal install split
And then use chunksOf from Data.List.Split
Here's one option:
partition :: Int -> [a] -> [[a]]
partition _ [] = []
partition n xs = (take n xs) : (partition n (drop n xs))
And here's a tail recursive version of that function:
partition :: Int -> [a] -> [[a]]
partition n xs = partition' n xs []
where
partition' _ [] acc = reverse acc
partition' n xs acc = partition' n (drop n xs) ((take n xs) : acc)
You could use:
mysteryFunction :: Int -> [a] -> [[a]]
mysteryFunction n list = unfoldr takeList list
where takeList [] = Nothing
takeList l = Just $ splitAt n l
or alternatively:
mysteryFunction :: Int -> [a] -> [[a]]
mysteryFunction n list = unfoldr (\l -> if null l then Nothing else Just $ splitAt n l) list
Note this puts any remaining elements in the last list, for example
mysteryFunction 2 "abcdefg" = ["ab", "cd", "ef", "g"]
import Data.List
import Data.Function
mysteryFunction n = map (map snd) . groupBy ((==) `on` fst) . zip ([0..] >>= replicate n)
... just kidding...
mysteryFunction x "" = []
mysteryFunction x s = take x s : mysteryFunction x (drop x s)
Probably not the elegant solution you had in mind.
There's already
Prelude Data.List> :t either
either :: (a -> c) -> (b -> c) -> Either a b -> c
and
Prelude Data.List> :t maybe
maybe :: b -> (a -> b) -> Maybe a -> b
so there really should be
list :: t -> ([a] -> t) -> [a] -> t
list n _ [] = n
list _ c xs = c xs
as well. With it,
import Data.List (unfoldr)
g n = unfoldr $ list Nothing (Just . splitAt n)
without it,
g n = takeWhile (not.null) . unfoldr (Just . splitAt n)
A fancy answer.
In the answers above you have to use splitAt, which is recursive, too. Let's see how we can build a recursive solution from scratch.
Functor L(X)=1+A*X can map X into a 1 or split it into a pair of A and X, and has List(A) as its minimal fixed point: List(A) can be mapped into 1+A*List(A) and back using a isomorphism; in other words, we have one way to decompose a non-empty list, and only one way to represent a empty list.
Functor F(X)=List(A)+A*X is similar, but the tail of the list is no longer a empty list - "1" - so the functor is able to extract a value A or turn X into a list of As. Then List(A) is its fixed point (but no longer the minimal fixed point), the functor can represent any given list as a List, or as a pair of a element and a list. In effect, any coalgebra can "stop" decomposing the list "at will".
{-# LANGUAGE DeriveFunctor #-}
import Data.Functor.Foldable
data N a x = Z [a] | S a x deriving (Functor)
(which is the same as adding the following trivial instance):
instance Functor (N a) where
fmap f (Z xs) = Z xs
fmap f (S x y) = S x $ f y
Consider the definition of hylomorphism:
hylo :: (f b -> b) -> (c -> f c) -> c -> b
hylo psi phi = psi . fmap (hylo psi phi) . phi
Given a seed value, it uses phi to produce f c, to which fmap applies hylo psi phi recursively, and psi then extracts b from the fmapped structure f b.
A hylomorphism for the pair of (co)algebras for this functor is a splitAt:
splitAt :: Int -> [a] -> ([a],[a])
splitAt n xs = hylo psi phi (n, xs) where
phi (n, []) = Z []
phi (0, xs) = Z xs
phi (n, (x:xs)) = S x (n-1, xs)
This coalgebra extracts a head, as long as there is a head to extract and the counter of extracted elements is not zero. This is because of how the functor was defined: as long as phi produces S x y, hylo will feed y into phi as the next seed; once Z xs is produced, functor no longer applies hylo psi phi to it, and the recursion stops.
At the same time hylo will re-map the structure into a pair of lists:
psi (Z ys) = ([], ys)
psi (S h (t, b)) = (h:t, b)
So now we know how splitAt works. We can extend that to splitList using apomorphism:
splitList :: Int -> [a] -> [[a]]
splitList n xs = apo (hylo psi phi) (n, xs) where
phi (n, []) = Z []
phi (0, xs) = Z xs
phi (n, (x:xs)) = S x (n-1, xs)
psi (Z []) = Cons [] $ Left []
psi (Z ys) = Cons [] $ Right (n, ys)
psi (S h (Cons t b)) = Cons (h:t) b
This time the re-mapping is fitted for use with apomorphism: as long as it is Right, apomorphism will keep using hylo psi phi to produce the next element of the list; if it is Left, it produces the rest of the list in one step (in this case, just finishes off the list with []).

Resources