Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enh(fsharp) Global overhaul #3348

Merged
merged 9 commits into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 53 additions & 30 deletions src/languages/fsharp.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
/*
Language: F#
Author: Jonas Follesø <[email protected]>
Contributors: Troy Kershaw <[email protected]>, Henrik Feldt <[email protected]>
Contributors: Troy Kershaw <[email protected]>, Henrik Feldt <[email protected]>, Melvyn Laïly <[email protected]>
Website: https://docs.microsoft.com/en-us/dotnet/fsharp/
Category: functional
*/

/** @type LanguageFn */
export default function(hljs) {

const GENERICTYPESYMBOL = {
begin: /('|\^)[a-zA-Z0-9_]+/,
scope: 'symbol'
};

const TYPEPARAM = {
begin: '<',
end: '>',
contains: [
hljs.inherit(hljs.TITLE_MODE, {
begin: /'[a-zA-Z0-9_]+/
})
GENERICTYPESYMBOL
]
};

Expand All @@ -39,6 +43,7 @@ export default function(hljs) {
"extern",
"false",
"finally",
"fixed",
"for",
"fun",
"function",
Expand All @@ -57,6 +62,7 @@ export default function(hljs) {
"mutable",
"namespace",
"new",
"not",
"null",
"of",
"open",
Expand All @@ -66,7 +72,6 @@ export default function(hljs) {
"public",
"rec",
"return",
"sig",
"static",
"struct",
"then",
Expand Down Expand Up @@ -94,30 +99,21 @@ export default function(hljs) {
illegal: /\/\*/,
contains: [
{
// monad builder keywords (matches before non-bang kws)
className: 'keyword',
begin: /\b(yield|return|let|do)!/
},
{
className: 'string',
begin: '@"',
end: '"',
contains: [
{
begin: '""'
}
]
// monad builder keywords (matches before non-bang keywords)
scope: 'keyword',
match: /\b(yield|return|let|do|match|use)!/
},
{
className: 'string',
begin: '"""',
end: '"""'
scope: 'string',
// matches triple quote strings, verbatim strings (@""), character literals...
match: /(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")|'(?:[^\\']|\\(?:.|\d{3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}|U[a-fA-F\d]{8}))'/
},
hljs.COMMENT('\\(\\*(\\s)', '\\*\\)', {
hljs.COMMENT(/\(\*(?!\))/, /\*\)/, {
contains: ["self"]
}),
{
className: 'class',
// type definitions:
scope: 'title.class',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

title.class is for names of classes such as WordParser, etc... (and class alone is deprecated) you may want a multi-matcher here... what is the syntax you're trying to cover?

Copy link
Contributor Author

@mlaily mlaily Oct 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this because I noticed class was deprecated. I thought title.class was the replacement.

The syntax I'm trying to cover is any type definition (the part before the first = or ():

type A = int * int // type abbreviation
type B = { FirstName: string; LastName: string } // record
type C = Circle of int | Rectangle of int * int // discriminated union
type D = Day | Month | Year // another discriminated union
type E<'a> = Choice1 of 'a | Choice2 of 'a * 'a // a generic type definition

type MyClass(initX: int) = // class
   let x = initX
   member _.Method() = printf "x=%i" x

It's currently parsed as follows:
image

I'm not sure I understand the meaning behind all the different standard scopes. What should I do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax I'm trying to cover is any type definition (the part before the first = or ():

We do not care/desire this type of "semantic" flagging (that what we deprecated when we deprecated class). IE we don't care about "class definitions". We only care about "class name/title" (which may or may not be in a definition). So above there should be NO nesting.

  • type is a keyword
  • A is a title.class
  • type A = int * int (overall is nothing)

beginKeywords: 'type',
end: '\\(|=|$',
excludeEnd: true,
Expand All @@ -127,20 +123,47 @@ export default function(hljs) {
]
},
{
className: 'meta',
begin: '\\[<',
end: '>\\]',
relevance: 10
// computation expressions:
beginScope: "emphasis",
match: /\b[_a-z]\w*(?=\s*\{)/i
},
{
// preprocessor directives and fsi commands:
scope: 'meta',
begin: '#',
end: '$',
keywords: {
keyword: 'if else endif line nowarn light r i I load time help quit'
}
},
{
// [<Attributes("")>]
scope: 'meta',
begin: /^\s*\[<(?=[<\w])/,
excludeBegin: true,
end: />\]/,
excludeEnd: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can also replace this excludeEnd with a look-ahead in the end regex, what do you think?

Should I also replace the excludeBegin with a begin regex in a look behind like so (with a look ahead for the expected content): /(?<=^\s*\[<)(?=\s*\w)/?

(and I just noticed the current regex has an extra < in the content look ahead that I inadvertently added when copy pasting from the csharp version...)

Copy link
Member

@joshgoebel joshgoebel Oct 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and resolved these [lookahead vs excludeEnd] in my commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I saw, but what about the excludeBegin? Should we also strive to replace them with look behinds?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also strive to replace them with look behinds?

No, because we can't use look-behinds at all yet - see prior discussion. When we support look-behind that's definitely something we'd have a look at.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't use look-behinds at all yet

Ah I did not understand that, I thought it was only negative look behind we couldn't use, my bad.

Why does the doc seems to indicate look-behinds in begin are supported?

Copy link
Member

@joshgoebel joshgoebel Oct 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key is in the wording:

look-behind matching (when JS supports it) for begin (#2135)

The key being "when JS [browsers] supports it". Nothing in our engine prevents them from working for begin - if the JS engine includes support. (as you've seen when using them initially) If you wanted to fork the library or use a 3rd party grammar that uses them - go for it - you just lose support for Mac OS Safari. At least one 3rd party library does feature detection - resulting in inconsistent behavior across browsers - but thats' not a tradeoff we're interested in for Core.

However, the engine itself would break if look-behind was used in end because of the way our engine is currently designed - the end matchers do not always have access to preceding content, so there is nothing to "look behind at".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'm still a bit confused by the subtlety, but ok ^^

Copy link
Member

@joshgoebel joshgoebel Oct 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relevance: 10,
contains: [
{
scope: 'string',
begin: /"/,
end: /"/
},
hljs.C_NUMBER_MODE
]
},
{
className: 'symbol',
begin: '\\B(\'[A-Za-z])\\b',
contains: [hljs.BACKSLASH_ESCAPE]
scope: 'operator',
// only non arithmetic operators that we can confidently match:
match: /\|{1,3}>|<\|{1,3}|->|<-|(?<!<)<<(?!<)|(?<!>)>>(?!>)|:>|:\?>?|\.\.|::|(?<!\|)\|(?!\|)/
},
GENERICTYPESYMBOL,
hljs.C_LINE_COMMENT_MODE,
hljs.inherit(hljs.QUOTE_STRING_MODE, {
illegal: null
}),
hljs.BINARY_NUMBER_MODE,
hljs.C_NUMBER_MODE
]
};
Expand Down
4 changes: 4 additions & 0 deletions test/markup/fsharp/attributes.expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<span class="hljs-comment">// Strings and numbers are highlighted inside the attribute</span>
[&lt;<span class="hljs-meta">Foo</span>&gt;]
[&lt;<span class="hljs-meta">Bar(<span class="hljs-string">&quot;bar&quot;</span>); Foo(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)</span>&gt;]
<span class="hljs-keyword">let</span> x () = ()
4 changes: 4 additions & 0 deletions test/markup/fsharp/attributes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Strings and numbers are highlighted inside the attribute
[<Foo>]
[<Bar("bar"); Foo(1, 2)>]
let x () = ()
40 changes: 35 additions & 5 deletions test/markup/fsharp/comments.expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,42 @@
<span class="hljs-comment">(*
here is a multi-line comment on
multiple lines

trying to break it:
<span class="hljs-comment">(**)</span>
/*
asdf
*/

<span class="hljs-comment">(* *)</span>

*)</span>

<span class="hljs-keyword">let</span> index =
len
|&gt; float
|&gt; Operators.(*) <span class="hljs-number">0.1</span> <span class="hljs-comment">// (*) here is not comment</span>
|&gt; Operators.(+) <span class="hljs-number">1</span> <span class="hljs-comment">// (+) here is not comment</span>
|&gt; Operators.(-) len <span class="hljs-comment">// (-) here is not comment</span>
;;
<span class="hljs-operator">|&gt;</span> float
<span class="hljs-operator">|&gt;</span> Operators.(*) <span class="hljs-number">0.1</span> <span class="hljs-comment">// (*) here is not comment</span>
<span class="hljs-operator">|&gt;</span> Operators.(+) <span class="hljs-number">1</span> <span class="hljs-comment">// (+) here is not comment</span>
<span class="hljs-operator">|&gt;</span> Operators.(-) len <span class="hljs-comment">// (-) here is not comment</span>


<span class="hljs-comment">// foobar</span>
<span class="hljs-comment">//bar</span>
<span class="hljs-comment">(**)</span>
<span class="hljs-comment">(*nospace*)</span>
<span class="hljs-comment">(* space *)</span>
<span class="hljs-comment">/// &lt;summary&gt;</span>
<span class="hljs-comment">/// Class level summary documentation goes here.</span>
<span class="hljs-comment">/// &lt;/summary&gt;</span>
<span class="hljs-comment">/// &lt;remarks&gt;</span>
<span class="hljs-comment">/// Longer comments can be associated with a type or member through</span>
<span class="hljs-comment">/// the remarks tag.</span>
<span class="hljs-comment">/// &lt;/remarks&gt;</span>
<span class="hljs-keyword">let</span> x = ()

<span class="hljs-comment">// the next one is not a comment</span>
(*) (*)

/*
this one is <span class="hljs-keyword">not</span> a valid comment either
*/
32 changes: 31 additions & 1 deletion test/markup/fsharp/comments.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
(*
here is a multi-line comment on
multiple lines

trying to break it:
(**)
/*
asdf
*/

(* *)

*)

let index =
Expand All @@ -11,4 +20,25 @@ let index =
|> Operators.(*) 0.1 // (*) here is not comment
|> Operators.(+) 1 // (+) here is not comment
|> Operators.(-) len // (-) here is not comment
;;


// foobar
//bar
(**)
(*nospace*)
(* space *)
/// <summary>
/// Class level summary documentation goes here.
/// </summary>
/// <remarks>
/// Longer comments can be associated with a type or member through
/// the remarks tag.
/// </remarks>
let x = ()

// the next one is not a comment
(*) (*)

/*
this one is not a valid comment either
*/
23 changes: 23 additions & 0 deletions test/markup/fsharp/computation-expressions.expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<span class="hljs-meta">#<span class="hljs-keyword">r</span> &quot;nuget: Ply&quot;</span>
<span class="hljs-keyword">open</span> FSharp.Control.Tasks
<span class="hljs-keyword">open</span> System.Threading.Tasks

<span class="hljs-comment">// Single line, and contains a capital letter</span>
<span class="hljs-keyword">let</span> unitTask = <span class="hljs-emphasis">unitTask</span> { <span class="hljs-keyword">return</span> () }

<span class="hljs-keyword">let</span> work =
<span class="hljs-emphasis">async</span> {
<span class="hljs-keyword">let</span> delayTask () =
<span class="hljs-comment">// Nested computation</span>
<span class="hljs-emphasis">task</span> {
printfn <span class="hljs-string">&quot;Delay...&quot;</span>
<span class="hljs-keyword">do!</span> Task.Delay <span class="hljs-number">1000</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">42</span>
}
<span class="hljs-keyword">let!</span> result = delayTask () <span class="hljs-operator">|&gt;</span> Async.AwaitTask
printfn <span class="hljs-string">&quot;Async F# sleep...&quot;</span>
<span class="hljs-keyword">do!</span> Async.Sleep <span class="hljs-number">1000</span>
<span class="hljs-keyword">return</span> result
}

<span class="hljs-keyword">let</span> result = work <span class="hljs-operator">|&gt;</span> Async.RunSynchronously
23 changes: 23 additions & 0 deletions test/markup/fsharp/computation-expressions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#r "nuget: Ply"
open FSharp.Control.Tasks
open System.Threading.Tasks

// Single line, and contains a capital letter
let unitTask = unitTask { return () }

let work =
async {
let delayTask () =
// Nested computation
task {
printfn "Delay..."
do! Task.Delay 1000
return 42
}
let! result = delayTask () |> Async.AwaitTask
printfn "Async F# sleep..."
do! Async.Sleep 1000
return result
}

let result = work |> Async.RunSynchronously
10 changes: 10 additions & 0 deletions test/markup/fsharp/fsi-and-preprocessor.expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<span class="hljs-meta">#<span class="hljs-keyword">r</span> &quot;file.dll&quot;;; // Reference (dynamically <span class="hljs-keyword">load</span>) the given DLL</span>
<span class="hljs-meta">#<span class="hljs-keyword">i</span> &quot;package source uri&quot;;; // Include package source uri when searching for packages</span>
<span class="hljs-meta">#<span class="hljs-keyword">I</span> &quot;path&quot;;; // Add the given search path for referenced DLLs</span>
<span class="hljs-meta">#<span class="hljs-keyword">load</span> &quot;file.fs&quot; ...;; // Load the given file(s) as <span class="hljs-keyword">if</span> compiled and referenced</span>
<span class="hljs-meta">#<span class="hljs-keyword">time</span> [&quot;on&quot;|&quot;off&quot;];; // Toggle timing on/off</span>
<span class="hljs-meta">#<span class="hljs-keyword">help</span>;; // Display <span class="hljs-keyword">help</span></span>
<span class="hljs-meta">#<span class="hljs-keyword">r</span> &quot;nuget:FSharp.Data, 3.1.2&quot;;; // Load Nuget Package &#x27;FSharp.Data&#x27; version &#x27;3.1.2&#x27;</span>
<span class="hljs-meta">#<span class="hljs-keyword">r</span> &quot;nuget:FSharp.Data&quot;;; // Load Nuget Package &#x27;FSharp.Data&#x27; with the highest version</span>
<span class="hljs-meta">#<span class="hljs-keyword">quit</span>;;</span>
10 changes: 10 additions & 0 deletions test/markup/fsharp/fsi-and-preprocessor.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

#r "file.dll";; // Reference (dynamically load) the given DLL
#i "package source uri";; // Include package source uri when searching for packages
#I "path";; // Add the given search path for referenced DLLs
#load "file.fs" ...;; // Load the given file(s) as if compiled and referenced
#time ["on"|"off"];; // Toggle timing on/off
#help;; // Display help
#r "nuget:FSharp.Data, 3.1.2";; // Load Nuget Package 'FSharp.Data' version '3.1.2'
#r "nuget:FSharp.Data";; // Load Nuget Package 'FSharp.Data' with the highest version
#quit;;
36 changes: 36 additions & 0 deletions test/markup/fsharp/generic-types.expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<span class="hljs-comment">// Primarily testing for generic parameters highlighting...</span>

<span class="hljs-title class_"><span class="hljs-keyword">type</span> <span class="hljs-title">Ref</span>&lt;<span class="hljs-symbol">&#x27;a</span>&gt; </span>=
{ <span class="hljs-keyword">mutable</span> contents: <span class="hljs-symbol">&#x27;a</span> }

<span class="hljs-title class_"><span class="hljs-keyword">type</span> <span class="hljs-title">Bla</span>&lt;<span class="hljs-symbol">&#x27;a</span>&gt; </span>= {X: string}
<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> asdf x: Bla&lt;<span class="hljs-symbol">&#x27;a</span>&gt; = {X = <span class="hljs-string">&quot;&quot;</span>}

<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> asdf x: Bla&lt;<span class="hljs-symbol">^a</span>&gt; = {X = <span class="hljs-string">&quot;&quot;</span>}
<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> asdf x: Bla&lt; <span class="hljs-symbol">^a</span> &gt; = {X = <span class="hljs-string">&quot;&quot;</span>}

<span class="hljs-keyword">let</span> genericSumUnits ( x : float&lt;<span class="hljs-symbol">&#x27;u</span>&gt;) (y: float&lt;<span class="hljs-symbol">&#x27;u</span>&gt;) = x + y

<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> konst x _ = x

<span class="hljs-title class_"><span class="hljs-keyword">type</span> <span class="hljs-title">CFunctor</span></span>() =
<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> <span class="hljs-keyword">inline</span> fmap (f: <span class="hljs-symbol">^a</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^b</span>, a: <span class="hljs-symbol">^a</span> list) = List.map f a
<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> <span class="hljs-keyword">inline</span> fmap (f: <span class="hljs-symbol">^a</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^b</span>, a: <span class="hljs-symbol">^a</span> option) =
<span class="hljs-keyword">match</span> a <span class="hljs-keyword">with</span>
<span class="hljs-operator">|</span> None <span class="hljs-operator">-&gt;</span> None
<span class="hljs-operator">|</span> Some x <span class="hljs-operator">-&gt;</span> Some (f x)

<span class="hljs-comment">// default implementation of replace</span>
<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> <span class="hljs-keyword">inline</span> replace&lt; <span class="hljs-symbol">^a</span>, <span class="hljs-symbol">^b</span>, <span class="hljs-symbol">^c</span>, <span class="hljs-symbol">^d</span>, <span class="hljs-symbol">^e</span> <span class="hljs-keyword">when</span> <span class="hljs-symbol">^a</span> <span class="hljs-operator">:&gt;</span> CFunctor <span class="hljs-keyword">and</span> (<span class="hljs-symbol">^a</span> <span class="hljs-keyword">or</span> <span class="hljs-symbol">^d</span>): (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> fmap: (<span class="hljs-symbol">^b</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^c</span>) * <span class="hljs-symbol">^d</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^e</span>) &gt; (a, f) =
((<span class="hljs-symbol">^a</span> <span class="hljs-keyword">or</span> <span class="hljs-symbol">^d</span>) : (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> fmap : (<span class="hljs-symbol">^b</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^c</span>) * <span class="hljs-symbol">^d</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^e</span>) (konst a, f))

<span class="hljs-comment">// call overridden replace if present</span>
<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> <span class="hljs-keyword">inline</span> replace&lt; <span class="hljs-symbol">^a</span>, <span class="hljs-symbol">^b</span>, <span class="hljs-symbol">^c</span> <span class="hljs-keyword">when</span> <span class="hljs-symbol">^b</span>: (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> replace: <span class="hljs-symbol">^a</span> * <span class="hljs-symbol">^b</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^c</span>)&gt;(a: <span class="hljs-symbol">^a</span>, f: <span class="hljs-symbol">^b</span>) =
(<span class="hljs-symbol">^b</span> : (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> replace: <span class="hljs-symbol">^a</span> * <span class="hljs-symbol">^b</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^c</span>) (a, f))

<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> replace_instance&lt; <span class="hljs-symbol">^a</span>, <span class="hljs-symbol">^b</span>, <span class="hljs-symbol">^c</span>, <span class="hljs-symbol">^d</span> <span class="hljs-keyword">when</span> (<span class="hljs-symbol">^a</span> <span class="hljs-keyword">or</span> <span class="hljs-symbol">^c</span>): (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> replace: <span class="hljs-symbol">^b</span> * <span class="hljs-symbol">^c</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^d</span>)&gt; (a: <span class="hljs-symbol">^b</span>, f: <span class="hljs-symbol">^c</span>) =
((<span class="hljs-symbol">^a</span> <span class="hljs-keyword">or</span> <span class="hljs-symbol">^c</span>): (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> replace: <span class="hljs-symbol">^b</span> * <span class="hljs-symbol">^c</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^d</span>) (a, f))

<span class="hljs-comment">// Note the concrete type &#x27;CFunctor&#x27; specified in the signature</span>
<span class="hljs-keyword">let</span> <span class="hljs-keyword">inline</span> replace (a: <span class="hljs-symbol">^a</span>) (f: <span class="hljs-symbol">^b</span>): <span class="hljs-symbol">^a0</span> <span class="hljs-keyword">when</span> (CFunctor <span class="hljs-keyword">or</span> <span class="hljs-symbol">^b</span>): (<span class="hljs-keyword">static</span> <span class="hljs-keyword">member</span> replace: <span class="hljs-symbol">^a</span> * <span class="hljs-symbol">^b</span> <span class="hljs-operator">-&gt;</span> <span class="hljs-symbol">^a0</span>) =
replace_instance&lt;CFunctor, _, _, _&gt; (a, f)
Loading