antalsz / hs-to-coq Goto Github PK
View Code? Open in Web Editor NEWConvert Haskell source code to Coq source code
Home Page: https://hs-to-coq.readthedocs.io
License: MIT License
Convert Haskell source code to Coq source code
Home Page: https://hs-to-coq.readthedocs.io
License: MIT License
Given this code (test case FTP.hs
, names changed from their base version to avoid clashes)
instance Foltable Pair where
foltMap f (Pair x y) = f x `mabbend` f y
instance Trafersable Pair where
traferse f (Pair x y) = purr Pair <**> f x <**> f y
instance Momoid a => Momoid (Pair a) where
mempti = Pair mempti mempti
(Pair a b) `mabbend` (Pair c d) = Pair (a `mabbend` b) (c `mabbend` d)
instance Abblicative Pair where
purr x = Pair x x
(Pair f1 f2) <**> (Pair x y) = Pair (f1 x) (f2 y)
instance Funktor Pair where
fmaap f (Pair a b) = Pair (f a) (f b)
The output needs to put them into the right order. In doing so, it needs to take superclass constraints into account! Because of
class (Funktor t, Foltable t) => Trafersable t where
traferse :: Abblicative f => (a -> f b) -> t a -> f (t b)
the Trafersable
instance needs to come after the Foltable
instance. This information is syntactically not very obvious.
An alternative to sorting might be parametrizing the instances, i.e. produce
Instance instance_Trafersable_Pair_24__ `{Funktor Pair} `{Foltable Pair} : !Trafersable Pair := {
traferse := fun {a} {b} {f} `{Abblicative f} => traferse_29__ }.
instead of
Instance instance_Trafersable_Pair_24__ : Trafersable Pair := {
traferse := fun {a} {b} {f} `{Abblicative f} => traferse_29__ }.
This defers the resolution of the superclass instances to when the instance is actually used.
When I convert GHC.Base using the -N flag, with the following skipped modules
skip module GHC.Arr
skip module GHC.Generics
skip module GHC.Types
skip module GHC.CString
skip module GHC.Classes
skip module GHC.Magic
skip module GHC.Err
skip module GHC.IO
skip module GHC.Integer
skip module GHC.Unicode
skip module GHC.Num
skip module GHC.Char
skip module GHC.Real
skip module Data.Either
skip module Data.Proxy
skip module GHC.Enum
skip module Data.Bits
skip module Data.Type.Equality
skip module GHC.Prim
skip module GHC.Tuple
skip module Control.Category
skip module Data.Functor.Const
skip module Data.IntMap
skip module Data.IntMap.Base
skip module Data.IntSet.Base
translation still fails on :~:
which is from Data.Type.Equality.
The normal skip
is a bit to weak, because a skipped method needs to be skipped both in the class definition and the instance definition.
Format:
skip method Monad fail
The Monad
is unqualified; this seemed to be the most convenient choice, given that this edit needs to be used both where a class is declared (and hence the name is unqualified) and where a class is referenced (where it can be qualified).
Need a skip to eliminate constructor from data type definitions and branches for that constructor from functions.
Because add
is a token keyword in the happy parser, I cannot write
skip add
Is there a way to avoid that?
When I translate ghc/compiler/Util/Pair.v (in examples/ghc)
I get these messages:
(* Translating `instance Data.Foldable.Foldable Pair' failed: OOPS! Cannot find
information for class "Data.Foldable.Foldable" unsupported *)
(* Translating `instance Data.Traversable.Traversable Pair' failed: OOPS! Cannot
find information for class "Data.Traversable.Traversable" unsupported *)
(* Translating `instance forall {a}, forall `{GHC.Base.Monoid a},
GHC.Base.Monoid (Pair a)' failed: OOPS! Cannot find information for class
"GHC.Base.Monoid" unsupported *)
Do we need to do something special to tell hs-to-coq where to find these classes?
*** No rule to make target '../../base/GHC/Base.vo', needed by 'Control/Applicative/Successors.vo'.
I have put a minimal example in examples/tests
. In summary, if you have the following functions:
comp :: (b -> c) -> (a -> b) -> a -> c
comp f g x = f (g x)
id2 :: (a -> a) -> (a -> a) -> a -> a
id2 = comp :: (a -> a) -> (a -> a) -> a -> a
hs-to-coq
will translate it to:
Definition comp {b} {c} {a} : (b -> c) -> (a -> b) -> a -> c :=
fun f g x => f (g x).
Definition id2 {a} : (a -> a) -> (a -> a) -> a -> a :=
comp : (forall {a}, (a -> a) -> (a -> a) -> a -> a).
Coq will complain that there is a type error:
Error:
In environment
a : Type
The term "comp" has type "(?b -> ?c) -> (?a -> ?b) -> ?a -> ?c"
while it is expected to have type
"forall a : Type, (a -> a) -> (a -> a) -> a -> a".
In order to support things such as correctly knowing data type constructors or default modules outside of base
, or correctly extending skip method
to modules that import a type class, we need to generate metadata files. Current plan:
When translating Mod/File.hs
, to $OUTPUT/Mod/File.v
, also generate $METADATA/Mod/File.h2c
, where $METADATA = $OUTPUT
by default.
Pass in search directories when converting a file. For example, if working with base
, you'd specify -M path/to/base/output
(flag name subject to change).
When translating a file that does import M
, hs-to-coq
would go and load M.h2c
. That would specify (a) what other h2c
files to import; (b) metadata, e.g. constructor names; and (c) "persistent" edits, such as skip method
or (I think) skip module
. If it had already been loaded or generated, this would be brought in from memory, instead of from disk.
Re. 3, however, one question is: would these changes have to be specific to the module that imported them, or could they be globally loaded? Information about the constructors of Full.Mod.Type
is always applicable because it's fully qualified. In that case, we could eagerly load all the .h2c
files, or possibly even only generate one per library rather than one per module.
We can't derive instances for newtypes because they use Data.Coerce. Can we pretend that they were written with 'data' instead?
If I try to load core.v
(version 8a65ab0) in coqide, it gives the error message
Anomaly: Unable to handle arbitrary u+k <= v constraints. Please report at
http://coq.inria.fr/bugs/.
as soon as the focus reaches Inductive Expr
. Is something wrong with the extracted Coq or with my local Coq setup (Coq 8.6)?
E.g. in the Monad Dual
instance, there is a Data.Monoid.getDual
instead of simply getDual
.
I believe I know the cause.
For the following Haskell source:
eqString :: String -> String -> Bool
eqString [] [] = True
eqString (c1:cs1) (c2:cs2) = c1 == c2 && cs1 `eqString` cs2
eqString _ _ = False
The output I get for the RHS on the second line is
(eqString (andb (c1 == c2) cs1) cs2)
instead of
(andb (c1 == c2) (eqString cs1 cs2))
(To test, uncomment skip eqString
in examples/ghc-base/Base/edits`)
Where should I look to debug this issue?
I have created a minimal example that can reproduce this bug under examples/guard
.
The Haskell file contains the following three functions:
f :: Int -> Int
f x | x == 1 = 1
| otherwise = 2
g :: Int -> Int
g 1 = 1
g _ = 2
h :: Int -> Int
h x = if x == 1 then 1 else 2
hs-to-coq
fails to translate f
.
It can translate g
but the translated Coq file cannot be compiled (because the translated code uses ==
, which is a notation must be imported from GHC.Base
).
It can successfully translate h
.
Making each edit take exactly one line is harsh for redefine / add edits.
Can we add a multiline-block to the parser so that instead of
redefine Local Definition instance_GHC_Base_Eq__Boxity_op_zsze__ : Boxity -> (Boxity -> bool) := fun arg_319__ arg_320__ => match arg_319__ , arg_320__ with | a , b => negb (instance_GHC_Base_Eq__Boxity_op_zeze__ a b) end.
we can write
redefine {{ Local Definition instance_GHC_Base_Eq__Boxity_op_zsze__ : Boxity -> (Boxity -> bool) :=
fun arg_319__ arg_320__ =>
match arg_319__ , arg_320__ with
| a , b => negb (instance_GHC_Base_Eq__Boxity_op_zeze__ a b)
end.
}}
There's a lot going on in this repository:
hs-to-coq
conversion tool itselfbase
libraryIt would be nice to split this up into multiple repositories, because they're conceptually separate. The downside, of course, is that the examples depend on base
which depends on hs-to-coq
.
This code (also in examples/test/Endo.v
)
module Endo where
import Prelude hiding (Monoid)
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
newtype Endo a = Endo { appEndo :: a -> a }
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
mconcat = foldr Endo.mappend Endo.mempty
produces
Local Definition instance_Monoid__Endo_a__mempty : (Endo a) :=
Mk_Endo id.
Note how a
is not bound!
Many base module edit files include this for newtype coercions
add Data.Monoid Instance Unpeel_Dual a : Unpeel (Dual a) a :=
Build_Unpeel _ _ getDual Mk_Dual.
We shouldn't have to add these via an edit, though. Should just generate them whenever we see a newtype definition.
Possibly due to the fix of #9, the following code does not compile:
module PolyInstance3 where
class N a where bar :: a
class N a => M a where foo :: a
instance N [a] where bar = []
instance M a => M [a] where foo = [foo]
The problem is that Coq makes the type argument of M
implicit, which is not helpful in
Instance instance_forall___M_a___M__list_a_ : !forall {a}, forall `{M a}, M (list a) := {
foo := instance_forall___M_a___M__list_a__foo }.
A better placed !
would help. Or maybe, instead of having to mess with bangs, make the first argument of M
explicit:
Arguments M _ {_}.
with a helpful name so that Print Assumptions
in the end tells the user how many and where axioms are used.
If we try to translate Data.Functor.Const, some of the output includes an extra type variable inst_k
that is unused. Coq can't figure out the kind of this variable and won't default it, so it gets rejected.
For example, here is part of the (newtype-derived) Ord instance:
Local Definition instance_forall___GHC_Base_Ord_a___GHC_Base_Ord__Const_a_b__compare {inst_a}
{inst_k} {inst_b} `{GHC.Base.Ord
inst_a} : Const inst_a
inst_b -> Const inst_a
inst_b -> comparison :=
GHC.Prim.coerce GHC.Base.compare.
I want a better overview of what is missing, so I’ll start creating tickets.
data Option a = Some a | None
somePred1 x = False
somePred2 x = False
foo x | Some True <- x = 1
foo x | Some False <- x = 2
foo x | somePred1 x = 3
foo x | somePred2 x = 3
produces
Definition foo := (fun __arg_2__ =>
(match __arg_2__ with
| x => (match x with
| (Mk_Some True) => #1
| _ => _(*MissingValue*)
end)
| x => (match x with
| (Mk_Some False) => #2
| _ => _(*MissingValue*)
end)
| x => (if (somePred1 x)
then #3
else _(*MissingValue*))
| x => (if (somePred2 x)
then #3
else _(*MissingValue*))
end)).
but should fall through.
If we switch from HANDMOD to MODULES for Control.Category, the translation succeeds but we miss an instance
(* Translating `instance Category GHC.Prim.arrow' failed: OOPS! Cannot find
information for class "Category" unsupported *)
The Category class is defined in the same file so the class information should be available.
I want to be able to insert arbitrary Coq code right before or after a particular point in the file. Not just at the preamble.
after blah {{ Definition ... }}
I don't think we need to parse the part between the {{ ... }}, just splice it into the file at the appropriate point.
Broke the proof when I redefined the definition of Eq_ for Either. Not sure how to fix it.
for less sleepless nights of people who prefer sound theorem provers ;-)
If type class instances use scoped type variables, they will fail.
There is an explicit comment in the function HsToCoq.ConvertHaskell.Declarations.Instances.topoSortInstance:
-- ASSUMPTION: type variables don't show up in terms. Broken
-- by ScopedTypeVariables.
This is not pressing, but I wanted to note down some thoughts before I lose them.
We currently Require
imported modules (no Import
), and every imported entity is addressed fully qualified (GHC.Base.map
). This is nice, as we don’t have to worry about Haskell’s complex import/export structure or have problems with definitions that have clashing names.
This unfortunately does not allow us to use nice notation, in particular for binary operators. All these are now prefix operators (with z-encoded names…).
Here are two variants to work around this:
All definitions become Local Definition
, all imports becomes Require Import
. Because the definitions are Local,
the Import
does not import them, so we are in basically the situation as above. But we do get all notations.
The disadvantage is that if we want the definitions to be in scope unqualified (e.g. for proving), then we cannot do that.
Slightly better: Every module defines all notations in a Module Notations. … End Notations.
block. We import a module Foo
with Require Foo. Import Foo.Notations
.
This should fix the problem with getting the names unqualified if we want to. A downside is the slightly more verbose importing (but I think it is fine.)
If we want to use Foo
’s notation inside Foo
, we can simply declare every Notation twice: Once in the code, right after its definition, and then again all Notations
at the end of the file, in the Notations
module.
Orthogonal issue: Do we want an operator +
in Foo
to be (also) available as _Foo.+_
, i.e. a infix-but-manually-qualified version?
Semigroup.hs
used the DefaultSignatures
extension to define the Semigroup
type class:
class Semigroup a where
(<>) :: a -> a -> a
default (<>) :: Monoid a => a -> a -> a
(<>) = mappend
sconcat :: NonEmpty a -> a
sconcat = ...
stimes :: Integral b => b -> a -> a
stimes = ...
Notice that the default method has a different type signature.
hs-to-coq
will translate the above type class into the following (wrong) Coq code:
Record Semigroup__Dict a := Semigroup__Dict_Build {
sappend__ : forall `{GHC.Base.Monoid a}, a -> a -> a ;
sconcat__ : Data.List.NonEmpty.NonEmpty a -> a ;
stimes__ : forall {b}, forall `{GHC.Real.Integral b}, b -> a -> a }.
The Monoid
constraint should not appear in the type signature.
Data.Set
contains the following definition:
elem = go
where STRICT_1_OF_2(go)
go _ Tip = False
go x (Bin _ y l r) = x == y || go x l || go x r
where STRICT_1_OF_2
is a macro defined as:
#define STRICT_1_OF_2(fn) fn arg _ | arg `seq` False = undefined
It is used to improve performance by avoiding unneeded laziness, but this does not matter in the translated Coq version, so I would like to skip this macro expansion, as expanding that macro raises the complexity of the function, and introduces undefined
.
An alternative way to handle this problem is that if we have some heuristics to tell the condition here (arg `seq` False
) always evaluates to False
, we can ignore this branch during translation.
I expect that we will have trouble proving anything useful about, say, CoreSubst
if functions like uniqAway
or other code that needs to get a fresh Unique
are not total.
But GHC’s implementation of Unique
is an Int
, which is too small to always have a fresh one. Further more, UniqueSupply
uses unsafePerformIO
and C code to efficiently create new Uniques.
I have the gut feeling that we will have to implement the types Unique
and UniqSupply
in Coq, using actual nat
(or maybe Char * Integer
) as the representation, and implement UniqSupply
purely, and have the code translation map operations involving Unique
and UniqSupply
use our implementation.
This code
data Pair a = Pair a a
currently produces
Inductive Pair a : Type := Mk_Pair : a -> (a -> (Pair a)).
with
Inductive Pair (a : Type) : Type := Mk_Pair : a -> a -> Pair a
For Mk_Pair: Argument a is implicit
For Pair: Argument scope is [type_scope]
For Mk_Pair: Argument scopes are [type_scope _ _]
I believe it should produce
Inductive Pair a : Type := Mk_Pair : a -> (a -> (Pair a)).
Arguments Mk_Pair {a}.
so that
Inductive Pair (a : Type) : Type := Mk_Pair : a -> a -> Pair a
For Mk_Pair: Argument a is implicit and maximally inserted
For Pair: Argument scope is [type_scope]
For Mk_Pair: Argument scopes are [type_scope _ _]
Otherwise partial applications as in
Local Definition traferse_29__ : (forall {a} {b} {f},
(forall `{Abblicative f}, (a -> (f b)) -> ((Pair a) -> (f (Pair b))))) :=
fun {a} {b} {f} `{Abblicative f} =>
fun arg_25__ arg_26__ =>
match arg_25__ , arg_26__ with
| f , (Mk_Pair x y) => ((purr Mk_Pair <**> f x) <**> f y)
end.
do not work.
We currently create instance names that are absurdly wrong:
instance_forall___GHC_Base_Monoid_a____GHC_Base_Monoid_b____GHC_Base_Monoid_c____GHC_Base_Monoid_d____GHC_Base_Monoid_e___GHC_Base_Monoid__a___b___c___d___e_
Also, since we base the name on the pretty-printed instance head, instances of types with symbols (which are translated to _
) have clashing names (this occurs for Generic
instances).
At some point the hope was that users would not have deal with these names; in this case this would all be fine. But the names show up in proofs, so we should make them concise and to the point.
I plan to change them to simply
ClassName__InstTyCon
when the instance is for a specific type constructor or
ClassName__a
if it is of the form instance Class a
.
This is good enough as long as everything in Haskell98 and one does not translate a module that has two instances where class and or instance type have the same base name, but different module names. But let's not let perfect be the enemy of usable.
In order to deal with both overlapping names between modules and, surprisingly, operator precedence, we need to move the conversion process after the renamer, instead of simply after the parser. The biggest change will be that RdrName
will become Name
, and then that certain PlaceHolder
s might turn into actual data.
Right now, everytime we do deriving Eq, we need to add
redefine Local Definition instance_GHC_Base_Eq__Boxity_op_zsze__ : Boxity -> (Boxity -> bool) := fun arg_319__ arg_320__ => match arg_319__ , arg_320__ with | a , b => negb (instance_GHC_Base_Eq__Boxity_op_zeze__ a b) end.
to the edits file.
Fixing this in a principled way is challenging. But for now, we can workaround by dropping /=
from the Eq
type class and defining /=
as a separate function.
The midamble.v file for GHC.Base defines the notation
Infix "<" := (op_zl__) (no associativity, at level 70).
However, this notation conflicts with Coq's default notation for lt
, the less-than proposition.
Furthermore, this notation is not defined in a notation scope or in a submodule, so any file that imports the Prelude imports it.
I prefer using the notation "<?" for this operation (in line with the rest of Coq's libraries) instead of Haskell's notation. This makes it more clear that it is a bool returning function, not a prop. Others may disagree --- please discuss.
But, regardless, we should edit Base to make this notation more controllable.
This comes up while trying to formalize Streams
not sure how relevant it is for GHC.
data Stream a = forall s. Stream (s -> Step s a) s
yields
Inductive Stream a : Type := Mk_Stream {s} : (s -> (Step s a)) -> (s -> (Stream a)).
which is invalid syntax; the {s}
needs to be on the right of the colon.
Test case in examples/tests/Existential
/
We have to do a topological sort to mix instance declarations and value declarations, and also to get instance declarations in the right order. Currently, this uses topoSortSentences
. I want to see if I can make this function, or another function, a bit better at preserving the original order, so that we have to emit less order
edits when things are in the right order in the source code already. examples/successors
is an example for that.
It would be convenient to be able to say
skip type Outputable.SDoc
and have it skip all functions that use that type.
This would replace individual skips of functions throughout the development, such as these from
module-edits/SrcLoc/edits.
pprUserRealSpan
pprUserSpan
I don't think it should skip type definitions that depend that type. I think we'll need ad hoc solutions for those cases. (Either skipping those types too, or redefining them so that they don't mention the skipped type.)
We cannot currently translate
newtype ArrowMonad a b = ArrowMonad (a () b)
because the type of a
is higher order. In this case I've been adding the correct definition to the preamble
Inductive ArrowMonad (a : Type -> Type -> Type) b : Type := Mk_ArrowMonad : ((a unit b) -> ArrowMonad a b).
along with an edit
rename value ArrowMonad = Mk_ArrowMonad
(Perhaps I could get a 'redefine' edit to work, but that hasn't worked out for me.)
It would be better if hs-to-coq knew about this datatype definition anyways. So something like
param kind ArrowMonad (a : Type -> Type -> Type) (b : Type)
would provide just the information we need.
This also needs to apply to classes, such as
Class Category (cat : Type -> Type -> Type) := {
id : forall {a}, cat a a ;
op_z2218U__ : forall {b} {c} {a}, cat b c -> cat a b -> cat a c }.
Since not all constructors are punned, it might be easier, more reliable and following the principle of least surprise if we simply require the user to rename
punned in an edit, and do no manual Mk_
munging at all. (Suggestion by @sweirich)
Data.Semigroup defines includes (<>) as a class member which conflicts with Coq's notation for not equals. I'd like to rename it to something else, but 'rename' edits don't work for class members.
Currently, the presence of any data/type families in the input prevents the whole file from being translated.
For example, see base/Data/Either.hs
We should just skip them (or eventually, translate them).
data Width = W8 | W16 | W32 | W64
| W80 -- Extended double-precision float,
-- used in x86 native codegen only.
-- (we use Ord, so it'd better be in this order)
| W128
| W256
| W512
deriving (Eq, Ord, Show)
The output of the derived Ord instance of this datatype looks weird. I'm not sure what is going on. Maybe something to do with Enum
?
The current code (introduced in #1) uses complex chains of single-constructor pattern matches even when it is not needed, such as in the definition of map
. At least in these simple cases, we should produce a single, complete pattern match also in Coq.
The current plan to deal with non-obvious termination, in particular in local fix
, is to disable Coq’s termination checker locally. This needs to be done.
We currently have this for CoreExpr:
Inductive Expr : (forall b, Type) := Mk_Var : (forall {b}, (Id -> (Expr b)))
| Mk_Lit : (forall {b}, (Literal -> (Expr b)))
| Mk_App : (forall {b}, (((Expr b)) -> (((Expr b)) -> (Expr b))))
| Mk_Lam : (forall {b}, (b -> (((Expr b)) -> (Expr b))))
| Mk_Let : (forall {b}, (((Bind b)) -> (((Expr b)) -> (Expr b))))
| Mk_Case : (forall {b},
(((Expr b)) -> (b -> (Type_ -> ((list (Alt b)) -> (Expr b))))))
| Mk_Cast : (forall {b}, (((Expr b)) -> (Coercion -> (Expr b))))
| Mk_Tick : (forall {b}, (((Tickish Id)) -> (((Expr b)) -> (Expr b))))
| Mk_Type : (forall {b}, (Type_ -> (Expr b)))
| Mk_Coercion : (forall {b}, (Coercion -> (Expr b)))
with Bind : (forall b, Type) := Mk_NonRec : (forall {b},
(b -> (((Expr b)) -> (Bind b))))
| Mk_Rec : (forall {b}, ((list (b * ((Expr b)))) -> (Bind b)))
where "'Arg'" := ((Synonym Arg__raw (fun b => ((Expr b)%type))))
and "'Alt'" := ((Synonym Alt__raw (fun b =>
(((AltCon * (list b)) * (Expr b))%type)))).
this is accepted by Coq, but it does not work well when defining a function over a CoreExpr
: If you are in, say, the Mk_App
case, you get a fresh type T
for the binders of the subexpresions. It needs be the same type.
This seems to work better:
Inductive Expr b : Type := Mk_Var : ((Id -> (Expr b)))
| Mk_Lit : ((Literal -> (Expr b)))
| Mk_App : ((((Expr b)) -> (((Expr b)) -> (Expr b))))
| Mk_Lam : ((b -> (((Expr b)) -> (Expr b))))
| Mk_Let : ((((Bind b)) -> (((Expr b)) -> (Expr b))))
| Mk_Case : ((((Expr b)) -> (b -> (Type_ -> ((list (((AltCon * (list b)) * (Expr b))%type)) -> (Expr b))))))
| Mk_Cast : ((((Expr b)) -> (Coercion -> (Expr b))))
| Mk_Tick : ((((Tickish Id)) -> (((Expr b)) -> (Expr b))))
| Mk_Type : ((Type_ -> (Expr b)))
| Mk_Coercion : ((Coercion -> (Expr b)))
with Bind b : Type := Mk_NonRec : ((b -> (((Expr b)) -> (Bind b))))
| Mk_Rec : (((list (b * ((Expr b)))) -> (Bind b))).
Notation "'Arg'" := ((Synonym Arg__raw (fun b => ((Expr b)%type)))).
Notation "'Alt'" := ((Synonym Alt__raw (fun b =>(((AltCon * (list b)) * (Expr b))%type)))).
(Not sure if there is a way of using the type synonyms in the Inductive
.)
Many uses of Panic.panic in CmmTypes.hs are with concrete result types (integer, width, etc).
If we have a class for default values, then we can safely parameterize the panic
function with this class.
Parameter panic {a} `{Default a} : String -> a
(This is not urgent, marking for future reference).
Data.Function
has this annoying definition that we can't currently handle.
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
(.*.) `on` f = \x y -> f x .*. f y
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.