News aggregator

Spinal Transplants

Haskell on Reddit - Sun, 07/05/2015 - 11:38am

Context is for the weak, let's jump right in!

λ let as = [1..] λ null $ as False λ null $ reverse as ^CInterrupted. λ let reverse' xs = assertSpine (reverse xs) xs λ reverse' [1..10] [10,9,8,7,6,5,4,3,2,1] λ null $ reverse' as False

Now, maybe you've never needed to check whether the reversal of an infinite list was empty, and that's ok.

But I found it interesting that for operations like reverse or sort, the structure of the output was identical to the structure of the output - the order of the elements changes, but the spine is the same shape. But the implementation of each of these doesn't take advantage of this - null (sort as) is O(n), not O(1).

So I came up with assertSpine, to transplant the spine of the input to the output using non-refutable patterns:

assertSpine :: [a] -> [b] -> [a] assertSpine ~[] [] = [] assertSpine ~(a:as) (_ : bs) = a : assertSpine as bs

(I chose the argument order so I could just use the Reader monad to do reverse' = assertSpine =<< reverse, sort' = assertSpine =<< sort, and id' = assertSpine =<< id, feel free to bikeshed).

Next I cranked out some tests with Criterion - using null and length to check the runtime for sppine-strict characteristics and head and sum to check the runtime of fullly strict characteristics:

main = do as <- (seq =<< sum) <$> replicateM 10000 randomIO :: IO [Int] defaultMain $ do (gname, f, f') <- [ ("id", id, id') , ("reverse", reverse, reverse') , ("sort", sort, sort') ] (tname, t) <- [ ("null", whnf . (null.)) , ("length", whnf . (length.)) , ("head", whnf . (head.)) , ("sum", whnf . (sum.)) ] let name = tname ++ "." ++ gname [ bench name (t f as), bench (name ++ "'") $ t f' as ]

With 10K element lists (a number chosen by an exteremely scientific method), I found:

  • id beat id' on all four measures (not suprising)

    | id | id' --------+---------------------+-------------------- null | 34.33 ns (2.065 ns) | 46.60 ns (2.282 ns) length | 26.86 μs (2.488 μs) | 604.7 μs (34.27 μs) head | 35.00 ns (2.029 ns) | 63.85 ns (3.665 ns) sum | 403.4 μs (14.27 μs) | 2.535 ms (146.0 μs)
  • reverse beat reverse' on length and sum, but was indistinguishable on head, and lost on null

    | reverse | reverse' --------+---------------------+-------------------- null | 81.77 μs (6.644 μs) | 46.43 ns (2.355 ns) length | 100.0 μs (5.651 μs) | 607.1 μs (44.65 μs) head | 81.04 μs (7.247 μs) | 79.33 μs (3.160 μs) sum | 563.2 μs (33.22 μs) | 3.068 ms (173.3 μs)
  • sort lost to sort' on null and length, but was indistinguishable on head, and won on sum

    | sort | sort' --------+---------------------+-------------------- null | 1.523 ms (83.32 μs) | 50.52 ns (2.642 ns) length | 8.748 ms (555.0 μs) | 619.9 μs (46.58 μs) head | 1.585 ms (254.3 μs) | 1.533 ms (107.6 μs) sum | 10.46 ms (586.6 μs) | 14.97 ms (985.2 μs)

If I get time today, I might try to crank out the memory overhead, or take a look at the Core.

It's certainly not "free", but there are definitely times when a spinal transplant can be adventageous. It all depends on what you're going to do to the data.

EDIT: Another discussion of this technique can be found here, with /u/apfelmus's withShape.

submitted by rampion
[link] [22 comments]
Categories: Incoming News

[ANN] sql-fragment : Type safe SQL query combinator

Haskell on Reddit - Sun, 07/05/2015 - 10:36am

I'm finally releasing sql-fragment and its companion sql-fragment-mysql-simple.

This is my first published package so all comments (about code, design, etc ...) are welcome. I haven't pushed it on hackage yet because I'm waiting for some feeback before doing so.


SQLFragment is a type safe SQL combinator based on the idea that, a SQL query

is a monoid joins can be deduced automatically from an join graph.

SQLFragment main intent is to allow to build easily complex query by reusing and combining pre-made fragments (which can be typed or typeless). This is especially useful when building reporting tools, when a lot of queries are similar and the results are either table or charts. In that case, query output can be used "raw" (.i.e a list of tuple or equivalent) and don't need to be mapped to any complex data type. Unlike many other SQL package, which make it hard to combine SQLFragment and String, SQLFragment makes it easy to write raw SQL if needed. Its purpose is to help write query quickly not make developper life hard. We trust the developper to not use "unsafe" string.

SQLFragment also provide support for dimensional units, HList records and automatic fragments generation from a database. The fragments generation use a space separated values file which can be generated from the database (see corresponding backend).

For more details look at the Database.SQLFragment.SQLFragment and Database.SQLFragment.Operators.

Synopsis Example

Let's say we have a table of customers, products, and orders, joining a customer to n products. I want to display in table the list of the customer which ordered the product 'blue T-shirt'.

With SQLFragment, supposing I have defined email and blue fragments so that

>>> toSelectQuery email "SELECT email FROM customers" >>> toSelectQuery blue "FROM product WHERE description = 'blue T-shirt'"

and the join graph as been properly set up in joins.

I can simply combine those two fragment using <>.

>>> toSelectQuery $ email <> blue !@! joins "SELECT email FROM customers JOIN orders ON ( = customer_id) JOIN products ON ( = product_id) WHERE products.description = 'blue T-shirt'" submitted by maxigit
[link] [7 comments]
Categories: Incoming News

Development tools survey results

Haskell on Reddit - Sat, 07/04/2015 - 3:36pm

I made a summary of the survey that recently surfaced on this sub-reddit. In the process I cleaned up the entries a bit, I hope I did not mistakenly changed anything in that process.

/u/acow emacs haskell-mode ghc-mod company-ghc hlint

/u/zorasterisk emacs haskell-mode

/u/I4dcQsEpLzTHvD1qhlDE vi

/u/ephrion vim ghc-mod syntastic hlint few other indentation/highlighting plugins sometimes ghcid running in a tmux

/u/zcleghern EclipseFP

/u/ranjitjhala atom linter-hdevtools hover-tooltips-hdevtools hasktags

/u/bheklilr Sublime Text 3 + SublimeHaskell ghc-mod hoogle stylish-haskell hlint misc others

/u/pycube emacs haskell-mode haskell-flycheck plain company-mode

/u/ekilek22 ghcid arion

/u/_skp Atom (with some plugins, but not ide-haskell) ghc-mod hasktags hoogle

/u/fractalsea IntelliJ + Haskforce ghc-mod hlint

/u/implicit_cast Atom ide-haskell

/u/alt_account10 vim hasktags ghcid hlint hoogle + hoogle-index

/u/lally emacs haskell-mode

/u/Peaker emacs haskell-mode ghci-ng

/u/cretan_bull emacs ghc-mod haskell-mode haskell-indentation-mode company-mode

/u/Vektorweg Geany IDE

/u/mallai SublimeText + SublimeHaskell ghc-mod hasktags hoogle

/u/andrewthad vim tmux

/u/drwebb emacs haskell-mode structured-haskell-mode haskell-flycheck hindent haskell-dash hlint tmux

/u/WarDaft Notepad++ ghcid

/u/tejon SublimeText 3 SublimeHaskell (hsdev branch) SublimeRepl hsdev hlint stylish-haskell

/u/Mob_Of_One emacs haskell-mode ghci

/u/maxigit vim tmux ghcid

/u/shishkabeb emacs ghc-mod hlint standard autocomplete

/u/tikhonjelvis emacs haskell-mode

/u/hvr_ emacs haskell-mode

/u/ndmitchell SublimeText ghcid

/u/cgibbard vim

/u/ch0wn vim hlint ghc-mod hsimport

/u/clrnd vim ghci

/u/Crandom Atom ghci

/u/semanticistZombie vim haskell-vim stylish-haskell hlint

/u/gelisam vim hoogle ghci

/u/bryangarza emacs haskell-mode

/u/get-your-shinebox vim haskell-vim-now ghcid

/u/edwardkmett vim hlint

/u/AndrasKovacs emacs haskell-mode

/u/kfound vim hdevtools hlint syntastic

submitted by cies010
[link] [18 comments]
Categories: Incoming News

Do we have a Monadic abstraction over the Alternative interface?

Haskell on Reddit - Sat, 07/04/2015 - 6:01am

Does there exist a monad-transformer, which uses the Monad interface to abstract over the MonadPlus/Alternative functionality? I'm asking because I've just implemented the thing and am wondering, whether I should publish it.

What does it do?

It allows to abstract over the following expression:

clause1 <|> clause2 <|> clause3

with a monadic interface:

runAlt $ do lift $ clause1 lift $ clause2 lift $ clause3 What's the purpose?

Convenient construction of action-trees. The usefulness becomes evident when the clauses themselves use the "do" syntax. E.g.,

runAlt $ do lift $ do action1 action2 lift $ do action3 action4 lift $ do action5 action6

which otherwise would be the folowing uneditable thing:

(do action1 action2) <|> (do action3 action4) <|> (do action5 action6)

In case the solution not published yet, I'm all ears for the suggestions for the name of the thing.

submitted by nikita-volkov
[link] [comment]
Categories: Incoming News

Edwin Brady: Cross-platform Compilers for Functional Languages

Planet Haskell - Sat, 07/04/2015 - 5:06am

I’ve just submitted a new draft, Cross-platform Compilers for Functional Languages. Abstract:

Modern software is often designed to run on a virtual machine, such as the JVM or .NET’s CLR. Increasingly, even the web browser is considered a target platform with the Javascript engine as its virtual machine. The choice of programming language for a project is therefore restricted to the languages which target the desired platform. As a result, an important consideration for a programming language designer is the platform to be targetted. For a language to be truly cross-platform, it must not only support different operating systems (e.g. Windows, OSX and Linux) but it must also target different virtual machine environments such as JVM, .NET, Javascript and others. In this paper, I describe how this problem is addressed in the Idris programming language. The overall compilation process involves a number of intermediate representations and Idris exposes an interface to each of these representations, allowing back ends for different target platforms to decide which is most appropriate. I show how to use these representations to retarget Idris for multiple platforms, and further show how to build a generic foreign function interface supporting multiple platforms.

Constructive comments and suggestions are welcome, particularly if you’ve tried implementing a code generator for Idris.

Categories: Offsite Blogs

List-based ini parser?

haskell-cafe - Sat, 07/04/2015 - 12:26am
Ok,I've looked at the packages google and hackage found (ini, hsini & ConfigFile), and can't use any of them for dealing with the ini files I'm being handed. The problem is they all parse the config file into Maps, and that doesn't seem to be an option. I need lists, because I have multiple sections with the same name that turn into a list of objects, as well as sections that can have multiple options with the same name that turn into multiple objects. Any chance I overlooked a parser? Or maybe some parsing options in ConfigFile? Any other advice on a library to do this? _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe< at >
Categories: Offsite Discussion

Don Syme receives a medal for F#

Lambda the Ultimate - Fri, 07/03/2015 - 1:16pm

Don Syme receives the Royal Academy of Engineering's Silver Medal for his work on F#. The citation reads:

F# is known for being a clear and more concise language that interoperates well with other systems, and is used in applications as diverse asanalysing the UK energy market to tackling money laundering. It allows programmers to write code with fewer bugs than other languages, so users can get their programme delivered to market both rapidly and accurately. Used by major enterprises in the UK and worldwide, F# is both cross-platform and open source, and includes innovative features such as unit-of-measure inference, asynchronous programming and type providers, which have in turn influenced later editions of C# and other industry languages.


Categories: Offsite Discussion

Brandon Simmons: Translating some stateful bit-twiddling to haskell

Planet Haskell - Fri, 07/03/2015 - 11:58am

I just started implementing SipHash in hashabler and wanted to share a nice way I found to translate stateful bit-twiddling code in C (which makes heavy use of bitwise assignment operators) to haskell.

I was working from the reference implementation. As you can see statefulness and mutability are an implicit part of how the algorithm is defined, as it modifies the states of the v variables.

#define SIPROUND \ do { \ v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \ v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \ v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \ v2 += v1; v1=ROTL(v1,17); v1 ^= v2; v2=ROTL(v2,32); \ } while(0) int siphash( uint8_t *out, const uint8_t *in, uint64_t inlen, const uint8_t *k ) { /* ... */ for ( ; in != end; in += 8 ) { m = U8TO64_LE( in ); v3 ^= m; TRACE; for( i=0; i<cROUNDS; ++i ) SIPROUND; v0 ^= m; }

I wanted to translate this sort of code as directly as possible (I’d already decided if it didn’t work on the first try I would burn my laptop and live in the woods, rather than debug this crap).

First we’ll use name shadowing to “fake” our mutable variables, making it easy to ensure we’re always dealing with the freshest values.

{-# OPTIONS_GHC -fno-warn-name-shadowing #-}

We’ll also use RecordWildCards to make it easy to capture the “current state” of these values, through folds and helper functions.

{-# LANGUAGE RecordWildCards #-}

And finally we use the trivial Identity monad (this trick I learned from Oleg) which gets us the proper scoping we want for our v values:

import Data.Functor.Identity

Here’s a bit of the haskell:

siphash :: Hashable a => SipKey -> a -> Word64 siphash (k0,k1) = \a-> runIdentity $ do let v0 = 0x736f6d6570736575 v1 = 0x646f72616e646f6d v2 = 0x6c7967656e657261 v3 = 0x7465646279746573 ... v3 <- return $ v3 `xor` k1; v2 <- return $ v2 `xor` k0; v1 <- return $ v1 `xor` k1; v0 <- return $ v0 `xor` k0; ... -- Initialize rest of SipState: let mPart = 0 bytesRemaining = 8 inlen = 0 SipState{ .. } <- return $ hash (SipState { .. }) a let !b = inlen `unsafeShiftL` 56 v3 <- return $ v3 `xor` b -- for( i=0; i<cROUNDS; ++i ) SIPROUND; (v0,v1,v2,v3) <- return $ sipRound v0 v1 v2 v3 (v0,v1,v2,v3) <- return $ sipRound v0 v1 v2 v3 v0 <- return $ v0 `xor` b ... (v0,v1,v2,v3) <- return $ sipRound v0 v1 v2 v3 return $! v0 `xor` v1 `xor` v2 `xor` v3

If you were really doing a lot of this sort of thing, you could even make a simple quasiquoter that could translate bitwise assignment into code like the above.

Categories: Offsite Blogs

Philip Wadler: Don Syme awarded Silver Medal by Royal Academy of Engineering

Planet Haskell - Fri, 07/03/2015 - 10:28am
  Congratulations, Don!
For over two decades, the Academy’s Silver Medals have recognised exceptional personal contributions from early- to mid-career engineers who have advanced the cause of engineering in the UK.
Three of the UK’s most talented engineers are to receive the Royal Academy of Engineering’s coveted Silver Medal for remarkable technical  achievements in their fields, coupled with commercial success.They are the inventor of 3D printed surgical instruments, an indoor location-tracking technology pioneer, and the creator of the F#
computer programming language. Full RAE Announcement. Spotted by Kevin Hammond.
Categories: Offsite Blogs

[GHC 7.8.4] IO Monad leak space?

haskell-cafe - Fri, 07/03/2015 - 9:18am
Hi, First of all, I found it interesting that loopM_ f k n s = when (k <= n) (f k >> loopM_ f (s+k) n s) loopM_ seems faster than mapM_ ( mapM_ f [k, k+s..n])) I think mapM_ is used very commonly, why it's performance is even lower than a hand-written loop function? 2nd, even I replace mapM_ with loopM_ from above, when chain IO action, it still can leak space. ( Because IO Monad (>>) need keep ``RealWorld s'' updated so that I/O actions can be done in-order? ) Consider below function: f3 :: UArray Int Int -> IOUArray Int Int64 -> Int -> IO () f3 u r i = let !v = u ! i in go (f31 v) i i where f31 v j = readArray r j >>= \v1 -> writeArray r j (v1 + (fromIntegral i) * (fromIntegral v)) f31 :: Int -> Int -> IO () go g k s = when (k <= maxn) ( g k >> go g (s+k) s ) When call f3: loopM_ (f3 uu res) 1 1 1000000 Which will have blow profiling output: individual inherited COST CENTRE MODULE no. entries %time %alloc %time %alloc ... loopM_ Main 104 4
Categories: Offsite Discussion

Mark Jason Dominus: The annoying boxes puzzle: solution

Planet Haskell - Fri, 07/03/2015 - 6:15am
I presented this logic puzzle on Wednesday:

There are two boxes on a table, one red and one green. One contains a treasure. The red box is labelled "exactly one of the labels is true". The green box is labelled "the treasure is in this box."

Can you figure out which box contains the treasure?

It's not too late to try to solve this before reading on. If you want, you can submit your answer here:

The treasure is in the red box
The treasure is in the green box
There is not enough information to determine the answer
Something else:
Results There were 506 total responses up to Fri Jul 3 11:09:52 2015 UTC; I kept only the first response from each IP address, leaving 451. I read all the "something else" submissions and where it seemed clear I recoded them as votes for "red", for "not enough information", or as spam. (Several people had the right answer but submitted "other" so they could explain themselves.) There was also one post attempted to attack my (nonexistent) SQL database. Sorry, Charlie; I'm not as stupid as I look.

66.52% 300 red 25.72 116 not-enough-info 3.55 16 green 2.00 9 other 1.55 7 spam 0.44 2 red-with-qualification 0.22 1 attack 100.00 451 TOTAL One-quarter of respondents got the right answer, that there is not enough information given to solve the problem, Two-thirds of respondents said the treasure was in the red box. This is wrong. The treasure is in the green box.

What? Let me show you. I stated:

There are two boxes on a table, one red and one green. One contains a treasure. The red box is labelled "exactly one of the labels is true". The green box is labelled "the treasure is in this box."

The labels are as I said. Everything I told you was literally true.

The treasure is definitely not in the red box.

No, it is actually in the green box.

(It's hard to see, but one of the items in the green box is the gold and diamond ring made in Vienna by my great-grandfather, which is unquestionably a real treasure.)

So if you said the treasure must be in the red box, you were simply mistaken. If you had a logical argument why the treasure had to be in the red box, your argument was fallacious, and you should pause and try to figure out what was wrong with it.

I will discuss it in detail below.

Solution The treasure is undeniably in the green box. However, correct answer to the puzzle is "no, you cannot figure out which box contains the treasure". There is not enough information given. (Notice that the question was not “Where is the treasure?” but “Can you figure out…?”)

(Fallacious) Argument A Many people erroneously conclude that the treasure is in the red box, using reasoning something like the following:

  1. Suppose the red label is true. Then exactly one label is true, and since the red label is true, the green label is false. Since it says that the treasure is in the green box, the treasure must really be in the red box.
  2. Now suppose that the red label is false. Then the green label must also be false. So again, the treasure is in the red box.
  3. Since both cases lead to the conclusion that the treasure is in the red box, that must be where it is.
What's wrong with argument A? Here are some responses people commonly have when I tell them that argument A is fallacious:

"If the treasure is in the green box, the red label is lying."

Not quite, but argument A explicitly considers the possibility that the red label was false, so what's the problem?

"If the treasure is in the green box, the red label is inconsistent."

It could be. Nothing in the puzzle statement ruled this out. But actually it's not inconsistent, it's just irrelevant.

"If the treasure is in the green box, the red label is meaningless."

Nonsense. The meaning is plain: it says “exactly one of these labels is true”, and the meaning is that exactly one of the labels is true. Anyone presenting argument A must have understood the label to mean that, and it is incoherent to understand it that way and then to turn around and say that it is meaningless! (I discussed this point in more detail in 2007.)

"But the treasure could have been in the red box."

True! But it is not, as you can see in the pictures. The puzzle does not give enough information to solve the problem. If you said that there was not enough information, then congratulations, you have the right answer. The answer produced by argument A is incontestably wrong, since it asserts that the treasure is in the red box, when it is not.

"The conditions supplied by the puzzle statement are inconsistent."

They certainly are not. Inconsistent systems do not have models, and in particular cannot exist in the real world. The photographs above demonstrate a real-world model that satisfies every condition posed by the puzzle, and so proves that it is consistent.

"But that's not fair! You could have made up any random garbage at all, and then told me afterwards that you had been lying."

Had I done that, it would have been an unfair puzzle. For example, suppose I opened the boxes at the end to reveal that there was no treasure at all. That would have directly contradicted my assertion that "One [box] contains a treasure". That would have been cheating, and I would deserve a kick in the ass.

But I did not do that. As the photograph shows, the boxes, their colors, their labels, and the disposition of the treasure are all exactly as I said. I did not make up a lie to trick you; I described a real situation, and asked whether people they could diagnose the location of the treasure.

(Two respondents accused me of making up lies. One said:

There is no treasure. Both labels are lying. Look at those boxes. Do you really think someone put a treasure in one of them just for this logic puzzle? What can I say? I _did_ do this. Some of us just have higher standards.)

"But what about the labels?"

Indeed! What about the labels?

The labels are worthless The labels are red herrings; the provide no information. Consider the following version of the puzzle:

There are two boxes on a table, one red and one green. One contains a treasure.

Which box contains the treasure?

Obviously, the problem cannot be solved from the information given.

Now consider this version:

There are two boxes on a table, one red and one green. One contains a treasure. The red box is labelled "gcoadd atniy fnck z fbi c rirpx hrfyrom". The green box is labelled "ofurb rz bzbsgtuuocxl ckddwdfiwzjwe ydtd."

Which box contains the treasure?

One is similarly at a loss here.

(By the way, people who said one label was meaningless: this is what a meaningless label looks like.)

There are two boxes on a table, one red and one green. One contains a treasure. The red box is labelled "exactly one of the labels is true". The green box is labelled "the treasure is in this box."

But then the janitor happens by. "Don't be confused by those labels," he says. "They were stuck on there by the previous owner of the boxes, who was an illiterate shoemaker who only spoke Serbian. I think he cut them out of a magazine because he liked the frilly borders."

Which box contains the treasure?

The point being that in the absence of additional information, there is no reason to believe that the labels give any information about the contents of the boxes, or about labels, or about anything at all. This should not come as a surprise to anyone. It is true not just in annoying puzzles, but in the world in general. A box labeled “fresh figs” might contain fresh figs, or spoiled figs, or angry hornets, or nothing at all. Order
What is the Name of this Book?

with kickback
no kickback Why doesn't every logic puzzle fall afoul of this problem? I said as part of the puzzle conditions that there was a treasure in one box. For a fair puzzle, I am required to tell the truth about the puzzle conditions. Otherwise I'm just being a jerk.

Typically the truth or falsity of the labels is part of the puzzle conditions. Here's a typical example, which I took from Raymond Smullyan's What is the name of this book? (problem 67a):

… She had the following inscriptions put on the caskets: GoldSilverLead THE PORTRAIT IS IN THIS CASKET THE PORTRAIT IS NOT IN THIS CASKET THE PORTRAIT IS NOT IN THE GOLD CASKET Portia explained to the suitor that of the three statements, at most one was true.

Which casket should the suitor choose [to find the portrait]?

Notice that the problem condition gives the suitor a certification about the truth of the labels, on which he may rely. In the quotation above, the certification is in boldface.

A well-constructed puzzle will always contain such a certification, something like “one label is true and one is false” or “on this island, each person always lies, or always tells the truth”. I went to _What is the Name of this Book?_ to get the example above, and found more than I had bargained for: problem 70 is exactly the annoying boxes problem! Smullyan says:

Good heavens, I can take any number of caskets that I please and put an object in one of them and then write any inscriptions at all on the lids; these sentences won't convey any information whatsoever. (Page 65)

Had I known that ahead of time, I doubt I would have written this post at all.

But why is this so surprising? I don't know.

Final notes 16 people correctly said that the treasure was in the green box. This has to be counted as a lucky guess, unacceptable as a solution to a logic puzzle.

One respondent referred me to a similar post on lesswrong.

I did warn you all that the puzzle was annoying.

I started writing this post in October 2007, and then it sat on the shelf until I got around to finding and photographing the boxes. A triumph of procrastination!

Categories: Offsite Blogs

stack + hdevtools -- together at last!

Haskell on Reddit - Thu, 07/02/2015 - 10:35pm

hdevtools previously had an issue where it wouldn't work with stack databases. There is a pull request currently that lets hdevtools work with stack projects.

I've tested it against all of my own Haskell projects and it works. If you'd like to give it a shot, please post your experience!

  1. Clone the repository here
  2. stack build && stack install
  3. Test hdevtools with your stack enabled projects
submitted by ephrion
[link] [39 comments]
Categories: Incoming News