Skip to content

Commit a83ff5b

Browse files
committed
Added 'force-elems'
0 parents  commit a83ff5b

File tree

8 files changed

+208
-0
lines changed

8 files changed

+208
-0
lines changed

force-elems/LICENSE

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Copyright Author name here (c) 2021
2+
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above
12+
copyright notice, this list of conditions and the following
13+
disclaimer in the documentation and/or other materials provided
14+
with the distribution.
15+
16+
* Neither the name of Author name here nor the names of other
17+
contributors may be used to endorse or promote products derived
18+
from this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

force-elems/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# `forceElems`
2+
3+
The following function "entangles" the elements of a list with its spine, so that whenever the outermost constructor gets forced, the head element also gets forced (unless the list is empty, in which case nothing extra happens):
4+
5+
```haskell
6+
forceElemsList :: [a] -> [a]
7+
forceElemsList = foldr ((:) $!) []
8+
```
9+
10+
Check out [Trouble in paradise: Fibonacci](https://github.com/effectfully/sketches/tree/master/trouble-in-paradise-fibonacci) if you're interested how such a thing can be useful.
11+
12+
Note that `forceElemsList` does not force the spine of its argument, it only ensures that whenever some consumer forces the spine of the list returned by `forceElemsList`, the elements of that list get forced as well.
13+
14+
Can you define a function that does the same, but for any `Traversable`? I.e. a function having the following type signature:
15+
16+
```haskell
17+
forceElems :: Traversable t => t a -> t a
18+
```
19+
20+
Rules:
21+
22+
1. just like `forceElemsList`, `forceElems` does not force the spine of its argument (and so can handle infinite structures)
23+
2. containers having constructors storing multiple elements of type `a` (the order in which those get forces does not matter) and multiple recursive occurrences are allowed, as well as polymorphic recursion
24+
3. no need to handle weird custom `Traversable` instances that do not agree with `DeriveTraversable`, like `fmap id` calls inserted manually in the middle of a `traverse` definition (although that particular case can be handled)
25+
26+
Replace `forceElems = undefined` in [`src/Lib.hs`](src/Lib.hs) with an actual definition of `forceElems`.
27+
28+
There's a small test suite, which I run with `stack test` (`cabal` should probably work as well, but I haven't checked).

force-elems/Setup.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain

force-elems/force-elems.cabal

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: force-elems
2+
version: 0.1.0.0
3+
-- synopsis:
4+
-- description:
5+
homepage: https://github.com/githubuser/force-elems#readme
6+
license: BSD3
7+
license-file: LICENSE
8+
author: Author name here
9+
maintainer: [email protected]
10+
copyright: 2021 Author name here
11+
category: Web
12+
build-type: Simple
13+
extra-source-files: README.md
14+
cabal-version: >=1.10
15+
16+
library
17+
hs-source-dirs: src
18+
exposed-modules: Lib
19+
build-depends: base >= 4.7 && < 5
20+
default-language: Haskell2010
21+
22+
test-suite force-elems-test
23+
type: exitcode-stdio-1.0
24+
main-is: Main.hs
25+
hs-source-dirs: test
26+
ghc-options: -O2
27+
build-depends: base >=4.7 && <5,
28+
HUnit,
29+
force-elems
30+
default-language: Haskell2010
31+
32+
source-repository head
33+
type: git
34+
location: https://github.com/githubuser/force-elems

force-elems/src/Lib.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Lib
2+
( forceElems
3+
) where
4+
5+
forceElems :: Traversable t => t a -> t a
6+
forceElems = undefined

force-elems/stack.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
resolver: lts-16.27
2+
3+
packages:
4+
- .

force-elems/stack.yaml.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This file was autogenerated by Stack.
2+
# You should not edit this file by hand.
3+
# For more information, please see the documentation at:
4+
# https://docs.haskellstack.org/en/stable/lock_files
5+
6+
packages: []
7+
snapshots:
8+
- completed:
9+
size: 533252
10+
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/27.yaml
11+
sha256: c2aaae52beeacf6a5727c1010f50e89d03869abfab6d2c2658ade9da8ed50c73
12+
original: lts-16.27

force-elems/test/Main.hs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{-# LANGUAGE DeriveTraversable #-}
2+
{-# LANGUAGE RankNTypes #-}
3+
4+
module Main where
5+
6+
import Lib
7+
8+
import Data.IORef
9+
import Data.List
10+
import System.IO.Unsafe
11+
import Test.HUnit
12+
13+
withTrace :: ((forall b. a -> b -> b) -> c) -> IO ([a], c)
14+
withTrace k = do
15+
xsVar <- newIORef id
16+
let z = k $ \x y -> unsafePerformIO $ y <$ modifyIORef xsVar (. (x :))
17+
ds <- unsafeInterleaveIO $ readIORef xsVar
18+
return (ds [], z)
19+
20+
tracesAndNats :: IO ([Int], [()])
21+
tracesAndNats =
22+
withTrace $ \trace ->
23+
let go n = trace n () : go (n + 1)
24+
in go 0
25+
26+
data Tree2 a
27+
= Fork2 (Tree2 a) a (Tree2 a) a
28+
deriving (Functor, Foldable, Traversable)
29+
30+
zigZag :: Tree2 a -> [a]
31+
zigZag = go False where
32+
go False (Fork2 _ x r _) = x : go True r
33+
go True (Fork2 l _ _ y) = y : go False l
34+
35+
tracesAndPathsTree2 :: IO ([String], Tree2 ())
36+
tracesAndPathsTree2 =
37+
withTrace $ \trace ->
38+
let go p =
39+
Fork2
40+
(go (p . ('l' :)))
41+
(trace (p "x") ())
42+
(go (p . ('r' :)))
43+
(trace (p "y") ())
44+
in go id
45+
46+
data Rose a
47+
= Rose a [Rose a]
48+
deriving (Functor, Foldable, Traversable)
49+
50+
rightmost :: Rose a -> [a]
51+
rightmost (Rose x rs) = x : if null rs then [] else rightmost $ last rs
52+
53+
tracesAndPathsRose :: IO ([[Int]], Rose ())
54+
tracesAndPathsRose =
55+
withTrace $ \trace ->
56+
let go n p =
57+
Rose
58+
(trace (p []) ())
59+
(map (\m -> go (n + 1) (p . (m :))) [0..n])
60+
in go 0 id
61+
62+
main :: IO ()
63+
main = runTestTTAndExit . TestList $ map TestCase
64+
[ do
65+
(traces, nats) <- tracesAndNats
66+
length (take 6 nats) @?= 6
67+
traces @?= []
68+
, do
69+
(traces, nats) <- tracesAndNats
70+
length (take 1 $ forceElems nats) @?= 1
71+
traces @?= [0]
72+
, do
73+
(traces, nats) <- tracesAndNats
74+
length (take 5 $ forceElems nats) @?= 5
75+
traces @?= [0, 1, 2, 3, 4]
76+
, do
77+
(traces, paths) <- tracesAndPathsTree2
78+
length (take 3 $ zigZag paths) @?= 3
79+
traces @?= []
80+
, do
81+
(traces, paths) <- tracesAndPathsTree2
82+
length (take 4 . zigZag $ forceElems paths) @?= 4
83+
sort traces @?= ["rlrx", "rlry", "rlx", "rly", "rx", "ry", "x", "y"]
84+
, do
85+
(traces, paths) <- tracesAndPathsRose
86+
length (take 2 $ rightmost paths) @?= 2
87+
traces @?= []
88+
, do
89+
(traces, paths) <- tracesAndPathsRose
90+
length (take 7 . rightmost $ forceElems paths) @?= 7
91+
traces @?= inits [0..5]
92+
]

0 commit comments

Comments
 (0)